Security context
Critical· 9.8GHSA-pfg4-p438-p874 CVE-2019-9081CWE-502Published May 14, 2022

Laravel Framework Deserialization Vulnerability

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

5.7.0 → fixed in 6.20.44

Details

The Illuminate component of Laravel Framework 5.7.x has a deserialization vulnerability that can lead to remote code execution if the content is controllable, related to the `__destruct` method of the PendingCommand class in `PendingCommand.php`.

The fix

Release delta 5.7.0 → 6.20.44 (contains the fix)

· Jul 16, 2018, 02:14 PM+1851650compare
tests/Database/DatabaseEloquentModelTest.php+42 42
@@ -227,11 +227,11 @@ public function testWithMethodCallsQueryBuilderCorrectlyWithArray()
public function testUpdateProcess()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery', 'updateTimestamps'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('where')->once()->with('id', '=', 1);
$query->shouldReceive('update')->once()->with(['name' => 'taylor'])->andReturn(1);
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->expects($this->once())->method('updateTimestamps');
$model->setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher'));
$events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($model), $model)->andReturn(true);
@@ -250,11 +250,11 @@ public function testUpdateProcess()
public function testUpdateProcessDoesntOverrideTimestamps()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('where')->once()->with('id', '=', 1);
$query->shouldReceive('update')->once()->with(['created_at' => 'foo', 'updated_at' => 'bar'])->andReturn(1);
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher'));
$events->shouldReceive('until');
$events->shouldReceive('fire');
@@ -269,9 +269,9 @@ public function testUpdateProcessDoesntOverrideTimestamps()
public function testSaveIsCancelledIfSavingEventReturnsFalse()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher'));
$events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($model), $model)->andReturn(false);
$model->exists = true;
@@ -281,9 +281,9 @@ public function testSaveIsCancelledIfSavingEventReturnsFalse()
public function testUpdateIsCancelledIfUpdatingEventReturnsFalse()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher'));
$events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($model), $model)->andReturn(true);
$events->shouldReceive('until')->once()->with('eloquent.updating: '.get_class($model), $model)->andReturn(false);
@@ -295,9 +295,9 @@ public function testUpdateIsCancelledIfUpdatingEventReturnsFalse()
public function testEventsCanBeFiredWithCustomEventObjects()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelEventObjectStub')->setMethods(['newQueryWithoutScopes'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelEventObjectStub')->setMethods(['newModelQuery'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher'));
$events->shouldReceive('until')->once()->with(m::type(EloquentModelSavingEventStub::class))->andReturn(false);
$model->exists = true;
@@ -307,12 +307,12 @@ public function testEventsCanBeFiredWithCustomEventObjects()
public function testUpdateProcessWithoutTimestamps()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelEventObjectStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'fireModelEvent'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelEventObjectStub')->setMethods(['newModelQuery', 'updateTimestamps', 'fireModelEvent'])->getMock();
$model->timestamps = false;
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('where')->once()->with('id', '=', 1);
$query->shouldReceive('update')->once()->with(['name' => 'taylor'])->andReturn(1);
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->expects($this->never())->method('updateTimestamps');
$model->expects($this->any())->method('fireModelEvent')->will($this->returnValue(true));
@@ -325,11 +325,11 @@ public function testUpdateProcessWithoutTimestamps()
public function testUpdateUsesOldPrimaryKey()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery', 'updateTimestamps'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('where')->once()->with('id', '=', 1);
$query->shouldReceive('update')->once()->with(['id' => 2, 'foo' => 'bar'])->andReturn(1);
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->expects($this->once())->method('updateTimestamps');
$model->setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher'));
$events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($model), $model)->andReturn(true);
@@ -456,11 +456,11 @@ public function testFromDateTime()
public function testInsertProcess()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'refresh'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery', 'updateTimestamps', 'refresh'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('insertGetId')->once()->with(['name' => 'taylor'], 'id')->andReturn(1);
$query->shouldReceive('getConnection')->once();
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->expects($this->once())->method('updateTimestamps');
$model->setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher'));
@@ -475,11 +475,11 @@ public function testInsertProcess()
$this->assertEquals(1, $model->id);
$this->assertTrue($model->exists);
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'refresh'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery', 'updateTimestamps', 'refresh'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('insert')->once()->with(['name' => 'taylor']);
$query->shouldReceive('getConnection')->once();
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->expects($this->once())->method('updateTimestamps');
$model->setIncrementing(false);
@@ -498,10 +498,10 @@ public function testInsertProcess()
public function testInsertIsCancelledIfCreatingEventReturnsFalse()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('getConnection')->once();
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher'));
$events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($model), $model)->andReturn(true);
$events->shouldReceive('until')->once()->with('eloquent.creating: '.get_class($model), $model)->andReturn(false);
@@ -512,11 +512,11 @@ public function testInsertIsCancelledIfCreatingEventReturnsFalse()
public function testDeleteProperlyDeletesModel()
{
- $model = $this->getMockBuilder('Illuminate\Database\Eloquent\Model')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'touchOwners'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Database\Eloquent\Model')->setMethods(['newModelQuery', 'updateTimestamps', 'touchOwners'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('where')->once()->with('id', '=', 1)->andReturn($query);
$query->shouldReceive('delete')->once();
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->expects($this->once())->method('touchOwners');
$model->exists = true;
$model->id = 1;
@@ -525,11 +525,11 @@ public function testDeleteProperlyDeletesModel()
public function testPushNoRelations()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'refresh'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery', 'updateTimestamps', 'refresh'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('insertGetId')->once()->with(['name' => 'taylor'], 'id')->andReturn(1);
$query->shouldReceive('getConnection')->once();
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->expects($this->once())->method('updateTimestamps');
$model->name = 'taylor';
@@ -542,11 +542,11 @@ public function testPushNoRelations()
public function testPushEmptyOneRelation()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'refresh'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery', 'updateTimestamps', 'refresh'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('insertGetId')->once()->with(['name' => 'taylor'], 'id')->andReturn(1);
$query->shouldReceive('getConnection')->once();
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->expects($this->once())->method('updateTimestamps');
$model->name = 'taylor';
@@ -561,20 +561,20 @@ public function testPushEmptyOneRelation()
public function testPushOneRelation()
{
- $related1 = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'refresh'])->getMock();
+ $related1 = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery', 'updateTimestamps', 'refresh'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('insertGetId')->once()->with(['name' => 'related1'], 'id')->andReturn(2);
$query->shouldReceive('getConnection')->once();
- $related1->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $related1->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$related1->expects($this->once())->method('updateTimestamps');
$related1->name = 'related1';
$related1->exists = false;
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'refresh'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery', 'updateTimestamps', 'refresh'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('insertGetId')->once()->with(['name' => 'taylor'], 'id')->andReturn(1);
$query->shouldReceive('getConnection')->once();
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->expects($this->once())->method('updateTimestamps');
$model->name = 'taylor';
@@ -592,11 +592,11 @@ public function testPushOneRelation()
public function testPushEmptyManyRelation()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'refresh'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery', 'updateTimestamps', 'refresh'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('insertGetId')->once()->with(['name' => 'taylor'], 'id')->andReturn(1);
$query->shouldReceive('getConnection')->once();
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->expects($this->once())->method('updateTimestamps');
$model->name = 'taylor';
@@ -611,29 +611,29 @@ public function testPushEmptyManyRelation()
public function testPushManyRelation()
{
- $related1 = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'refresh'])->getMock();
+ $related1 = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery', 'updateTimestamps', 'refresh'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('insertGetId')->once()->with(['name' => 'related1'], 'id')->andReturn(2);
$query->shouldReceive('getConnection')->once();
- $related1->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $related1->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$related1->expects($this->once())->method('updateTimestamps');
$related1->name = 'related1';
$related1->exists = false;
- $related2 = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'refresh'])->getMock();
+ $related2 = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery', 'updateTimestamps', 'refresh'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('insertGetId')->once()->with(['name' => 'related2'], 'id')->andReturn(3);
$query->shouldReceive('getConnection')->once();
- $related2->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $related2->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$related2->expects($this->once())->method('updateTimestamps');
$related2->name = 'related2';
$related2->exists = false;
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'refresh'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery', 'updateTimestamps', 'refresh'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('insertGetId')->once()->with(['name' => 'taylor'], 'id')->andReturn(1);
$query->shouldReceive('getConnection')->once();
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$model->expects($this->once())->method('updateTimestamps');
$model->name = 'taylor';
@@ -1619,11 +1619,11 @@ public function testNonExistingAttributeWithInternalMethodNameDoesntCallMethod()
public function testIntKeyTypePreserved()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'refresh'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentModelStub')->setMethods(['newModelQuery', 'updateTimestamps', 'refresh'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('insertGetId')->once()->with([], 'id')->andReturn(1);
$query->shouldReceive('getConnection')->once();
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$this->assertTrue($model->save());
$this->assertEquals(1, $model->id);
@@ -1631,11 +1631,11 @@ public function testIntKeyTypePreserved()
public function testStringKeyTypePreserved()
{
- $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentKeyTypeModelStub')->setMethods(['newQueryWithoutScopes', 'updateTimestamps', 'refresh'])->getMock();
+ $model = $this->getMockBuilder('Illuminate\Tests\Database\EloquentKeyTypeModelStub')->setMethods(['newModelQuery', 'updateTimestamps', 'refresh'])->getMock();
$query = m::mock('Illuminate\Database\Eloquent\Builder');
$query->shouldReceive('insertGetId')->once()->with([], 'id')->andReturn('string id');
$query->shouldReceive('getConnection')->once();
- $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query));
+ $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query));
$this->assertTrue($model->save());
$this->assertEquals('string id', $model->id);
tests/Broadcasting/PusherBroadcasterTest.php+253 0
@@ -0,0 +1,253 @@
+<?php
+
+namespace Illuminate\Tests\Broadcasting;
+
+use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
+use Mockery as m;
+use PHPUnit\Framework\TestCase;
+
+class PusherBroadcasterTest extends TestCase
+{
+ /**
+ * @var \Illuminate\Broadcasting\Broadcasters\PusherBroadcaster
+ */
+ public $broadcaster;
+
+ public $pusher;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->pusher = m::mock('Pusher\Pusher');
+ $this->broadcaster = m::mock(PusherBroadcaster::class, [$this->pusher])->makePartial();
+ }
+
+ /**
+ * @dataProvider channelsProvider
+ */
+ public function testChannelNameNormalization($requestChannelName, $normalizedName)
+ {
+ $this->assertEquals(
+ $normalizedName,
+ $this->broadcaster->normalizeChannelName($requestChannelName)
+ );
+ }
+
+ /**
+ * @dataProvider channelsProvider
+ */
+ public function testIsGuardedChannel($requestChannelName, $_, $guarded)
+ {
+ $this->assertEquals(
+ $guarded,
+ $this->broadcaster->isGuardedChannel($requestChannelName)
+ );
+ }
+
+ public function testAuthCallValidAuthenticationResponseWithPrivateChannelWhenCallbackReturnTrue()
+ {
+ $this->broadcaster->channel('test', function() {
+ return true;
+ });
+
+ $this->broadcaster->shouldReceive('validAuthenticationResponse')
+ ->once();
+
+ $this->broadcaster->auth(
+ $this->getMockRequestWithUserForChannel('private-test')
+ );
+ }
+
+ /**
+ * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+ */
+ public function testAuthThrowAccessDeniedHttpExceptionWithPrivateChannelWhenCallbackReturnFalse()
+ {
+ $this->broadcaster->channel('test', function() {
+ return false;
+ });
+
+ $this->broadcaster->auth(
+ $this->getMockRequestWithUserForChannel('private-test')
+ );
+ }
+
+ /**
+ * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+ */
+ public function testAuthThrowAccessDeniedHttpExceptionWithPrivateChannelWhenRequestUserNotFound()
+ {
+ $this->broadcaster->channel('test', function() {
+ return true;
+ });
+
+ $this->broadcaster->auth(
+ $this->getMockRequestWithoutUserForChannel('private-test')
+ );
+ }
+
+ public function testAuthCallValidAuthenticationResponseWithPresenceChannelWhenCallbackReturnAnArray()
+ {
+ $returnData = [1, 2, 3, 4];
+ $this->broadcaster->channel('test', function() use ($returnData) {
+ return $returnData;
+ });
+
+ $this->broadcaster->shouldReceive('validAuthenticationResponse')
+ ->once();
+
+ $this->broadcaster->auth(
+ $this->getMockRequestWithUserForChannel('presence-test')
+ );
+ }
+
+ /**
+ * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+ */
+ public function testAuthThrowAccessDeniedHttpExceptionWithPresenceChannelWhenCallbackReturnNull()
+ {
+ $this->broadcaster->channel('test', function() {
+ return;
+ });
+
+ $this->broadcaster->auth(
+ $this->getMockRequestWithUserForChannel('presence-test')
+ );
+ }
+
+ /**
+ * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+ */
+ public function testAuthThrowAccessDeniedHttpExceptionWithPresenceChannelWhenRequestUserNotFound()
+ {
+ $this->broadcaster->channel('test', function() {
+ return [1, 2, 3, 4];
+ });
+
+ $this->broadcaster->auth(
+ $this->getMockRequestWithoutUserForChannel('presence-test')
+ );
+ }
+
+ public function testValidAuthenticationResponseCallPusherSocketAuthMethodWithPrivateChannel()
+ {
+ $request = $this->getMockRequestWithUserForChannel('private-test');
+
+ $data = [
+ 'auth' => 'abcd:efgh'
+ ];
+
+ $this->pusher->shouldReceive('socket_auth')
+ ->once()
+ ->andReturn(json_encode($data));
+
+ $this->assertEquals(
+ $data,
+ $this->broadcaster->validAuthenticationResponse($request, true)
+ );
+ }
+
+ public function testValidAuthenticationResponseCallPusherPresenceAuthMethodWithPresenceChannel()
+ {
+ $request = $this->getMockRequestWithUserForChannel('presence-test');
+
+ $data = [
+ 'auth' => 'abcd:efgh',
+ 'channel_data' => [
+ 'user_id' => 42,
+ 'user_info' => [1, 2, 3, 4],
+ ],
+ ];
+
+ $this->pusher->shouldReceive('presence_auth')
+ ->once()
+ ->andReturn(json_encode($data));
+
+ $this->assertEquals(
+ $data,
+ $this->broadcaster->validAuthenticationResponse($request, true)
+ );
+ }
+
+ public function channelsProvider()
+ {
+ $prefixesInfos = [
+ ['prefix' => 'private-', 'guarded' => true],
+ ['prefix' => 'presence-', 'guarded' => true],
+ ['prefix' => '', 'guarded' => false],
+ ];
+
+ $channels = [
+ 'test',
+ 'test-channel',
+ 'test-private-channel',
+ 'test-presence-channel',
+ 'abcd.efgh',
+ 'abcd.efgh.ijkl',
+ 'test.{param}',
+ 'test-{param}',
+ '{a}.{b}',
+ '{a}-{b}',
+ '{a}-{b}.{c}',
+ ];
+
+ $tests = [];
+ foreach ($prefixesInfos as $prefixInfos) {
+ foreach ($channels as $channel) {
+ $tests[] = [
+ $prefixInfos['prefix'] . $channel,
+ $channel,
+ $prefixInfos['guarded'],
+ ];
+ }
+ }
+
+ $tests[] = ['private-private-test' , 'private-test', true];
+ $tests[] = ['private-presence-test' , 'presence-test', true];
+ $tests[] = ['presence-private-test' , 'private-test', true];
+ $tests[] = ['presence-presence-test' , 'presence-test', true];
+ $tests[] = ['public-test' , 'public-test', false];
+
+ return $tests;
+ }
+
+ /**
+ * @param string $channel
+ * @return \Illuminate\Http\Request
+ */
+ protected function getMockRequestWithUserForChannel($channel)
+ {
+ $request = m::mock(\Illuminate\Http\Request::class);
+ $request->channel_name = $channel;
+ $request->socket_id = 'abcd.1234';
+
+ $request->shouldReceive('input')
+ ->with('callback', false)
+ ->andReturn(false);
+
+ $user = m::mock('User');
+ $user->shouldReceive('getAuthIdentifier')
+ ->andReturn(42);
+
+ $request->shouldReceive('user')
+ ->andReturn($user);
+
+ return $request;
+ }
+
+ /**
+ * @param string $channel
+ * @return \Illuminate\Http\Request
+ */
+ protected function getMockRequestWithoutUserForChannel($channel)
+ {
+ $request = m::mock(\Illuminate\Http\Request::class);
+ $request->channel_name = $channel;
+
+ $request->shouldReceive('user')
+ ->andReturn(null);
+
+ return $request;
+ }
+}
src/Illuminate/Notifications/composer.json+10 10
@@ -15,14 +15,14 @@
],
"require": {
"php": "^7.1.3",
- "illuminate/broadcasting": "5.7.*",
- "illuminate/bus": "5.7.*",
- "illuminate/container": "5.7.*",
- "illuminate/contracts": "5.7.*",
- "illuminate/filesystem": "5.7.*",
- "illuminate/mail": "5.7.*",
- "illuminate/queue": "5.7.*",
- "illuminate/support": "5.7.*"
+ "illuminate/broadcasting": "5.8.*",
+ "illuminate/bus": "5.8.*",
+ "illuminate/container": "5.8.*",
+ "illuminate/contracts": "5.8.*",
+ "illuminate/filesystem": "5.8.*",
+ "illuminate/mail": "5.8.*",
+ "illuminate/queue": "5.8.*",
+ "illuminate/support": "5.8.*"
},
"autoload": {
"psr-4": {
@@ -31,12 +31,12 @@
},
"extra": {
"branch-alias": {
- "dev-master": "5.7-dev"
+ "dev-master": "5.8-dev"
}
},
"suggest": {
"guzzlehttp/guzzle": "Required to use the Slack transport (^6.0)",
- "illuminate/database": "Required to use the database transport (5.7.*).",
+ "illuminate/database": "Required to use the database transport (5.8.*).",
"nexmo/client": "Required to use the Nexmo transport (^1.0)."
},
"config": {
tests/Broadcasting/RedisBroadcasterTest.php+241 0
@@ -0,0 +1,241 @@
+<?php
+
+namespace Illuminate\Tests\Broadcasting;
+
+use Illuminate\Broadcasting\Broadcasters\RedisBroadcaster;
+use Mockery as m;
+use PHPUnit\Framework\TestCase;
+
+class RedisBroadcasterTest extends TestCase
+{
+ /**
+ * @var \Illuminate\Broadcasting\Broadcasters\RedisBroadcaster
+ */
+ public $broadcaster;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->broadcaster = m::mock(RedisBroadcaster::class)->makePartial();
+ }
+
+ public function tearDown()
+ {
+ m::close();
+ }
+
+ /**
+ * @dataProvider channelsProvider
+ */
+ public function testChannelNameNormalization($requestChannelName, $normalizedName)
+ {
+ $this->assertEquals(
+ $normalizedName,
+ $this->broadcaster->normalizeChannelName($requestChannelName)
+ );
+ }
+
+ /**
+ * @dataProvider channelsProvider
+ */
+ public function testIsGuardedChannel($requestChannelName, $_, $guarded)
+ {
+ $this->assertEquals(
+ $guarded,
+ $this->broadcaster->isGuardedChannel($requestChannelName)
+ );
+ }
+
+ public function testAuthCallValidAuthenticationResponseWithPrivateChannelWhenCallbackReturnTrue()
+ {
+ $this->broadcaster->channel('test', function() {
+ return true;
+ });
+
+ $this->broadcaster->shouldReceive('validAuthenticationResponse')
+ ->once();
+
+ $this->broadcaster->auth(
+ $this->getMockRequestWithUserForChannel('private-test')
+ );
+ }
+
+ /**
+ * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+ */
+ public function testAuthThrowAccessDeniedHttpExceptionWithPrivateChannelWhenCallbackReturnFalse()
+ {
+ $this->broadcaster->channel('test', function() {
+ return false;
+ });
+
+ $this->broadcaster->auth(
+ $this->getMockRequestWithUserForChannel('private-test')
+ );
+ }
+
+ /**
+ * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+ */
+ public function testAuthThrowAccessDeniedHttpExceptionWithPrivateChannelWhenRequestUserNotFound()
+ {
+ $this->broadcaster->channel('test', function() {
+ return true;
+ });
+
+ $this->broadcaster->auth(
+ $this->getMockRequestWithoutUserForChannel('private-test')
+ );
+ }
+
+ public function testAuthCallValidAuthenticationResponseWithPresenceChannelWhenCallbackReturnAnArray()
+ {
+ $returnData = [1, 2, 3, 4];
+ $this->broadcaster->channel('test', function() use ($returnData) {
+ return $returnData;
+ });
+
+ $this->broadcaster->shouldReceive('validAuthenticationResponse')
+ ->once();
+
+ $this->broadcaster->auth(
+ $this->getMockRequestWithUserForChannel('presence-test')
+ );
+ }
+
+ /**
+ * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+ */
+ public function testAuthThrowAccessDeniedHttpExceptionWithPresenceChannelWhenCallbackReturnNull()
+ {
+ $this->broadcaster->channel('test', function() {
+ return;
+ });
+
+ $this->broadcaster->auth(
+ $this->getMockRequestWithUserForChannel('presence-test')
+ );
+ }
+
+ /**
+ * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+ */
+ public function testAuthThrowAccessDeniedHttpExceptionWithPresenceChannelWhenRequestUserNotFound()
+ {
+ $this->broadcaster->channel('test', function() {
+ return [1, 2, 3, 4];
+ });
+
+ $this->broadcaster->auth(
+ $this->getMockRequestWithoutUserForChannel('presence-test')
+ );
+ }
+
+ public function testValidAuthenticationResponseWithPrivateChannel()
+ {
+ $request = $this->getMockRequestWithUserForChannel('private-test');
+
+ $this->assertEquals(
+ json_encode(true),
+ $this->broadcaster->validAuthenticationResponse($request, true)
+ );
+ }
+
+ public function testValidAuthenticationResponseWithPresenceChannel()
+ {
+ $request = $this->getMockRequestWithUserForChannel('presence-test');
+
+ $this->assertEquals(
+ json_encode([
+ 'channel_data' => [
+ 'user_id' => 42,
+ 'user_info' => [
+ 'a' => 'b',
+ 'c' => 'd',
+ ],
+ ],
+ ]),
+ $this->broadcaster->validAuthenticationResponse($request, [
+ 'a' => 'b',
+ 'c' => 'd'
+ ])
+ );
+ }
+
+ public function channelsProvider()
+ {
+ $prefixesInfos = [
+ ['prefix' => 'private-', 'guarded' => true],
+ ['prefix' => 'presence-', 'guarded' => true],
+ ['prefix' => '', 'guarded' => false],
+ ];
+
+ $channels = [
+ 'test',
+ 'test-channel',
+ 'test-private-channel',
+ 'test-presence-channel',
+ 'abcd.efgh',
+ 'abcd.efgh.ijkl',
+ 'test.{param}',
+ 'test-{param}',
+ '{a}.{b}',
+ '{a}-{b}',
+ '{a}-{b}.{c}',
+ ];
+
+ $tests = [];
+ foreach ($prefixesInfos as $prefixInfos) {
+ foreach ($channels as $channel) {
+ $tests[] = [
+ $prefixInfos['prefix'] . $channel,
+ $channel,
+ $prefixInfos['guarded'],
+ ];
+ }
+ }
+
+ $tests[] = ['private-private-test' , 'private-test', true];
+ $tests[] = ['private-presence-test' , 'presence-test', true];
+ $tests[] = ['presence-private-test' , 'private-test', true];
+ $tests[] = ['presence-presence-test' , 'presence-test', true];
+ $tests[] = ['public-test' , 'public-test', false];
+
+ return $tests;
+ }
+
+ /**
+ * @param string $channel
+ * @return \Illuminate\Http\Request
+ */
+ protected function getMockRequestWithUserForChannel($channel)
+ {
+ $request = m::mock(\Illuminate\Http\Request::class);
+ $request->channel_name = $channel;
+
+ $user = m::mock('User');
+ $user->shouldReceive('getAuthIdentifier')
+ ->andReturn(42);
+
+ $request->shouldReceive('user')
+ ->andReturn($user);
+
+ return $request;
+ }
+
+ /**
+ * @param string $channel
+ * @return \Illuminate\Http\Request
+ */
+ protected function getMockRequestWithoutUserForChannel($channel)
+ {
+ $request = m::mock(\Illuminate\Http\Request::class);
+ $request->channel_name = $channel;
+
+ $request->shouldReceive('user')
+ ->andReturn(null);
+
+ return $request;
+ }
+}
src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
tests/Broadcasting/BroadcasterTest.php+140 0
@@ -94,6 +94,131 @@ public function testNotFoundThrowsHttpException()
};
$broadcaster->extractAuthParameters('asd.{model}', 'asd.1', $callback);
}
+
+ public function testCanRegisterChannelsWithoutOptions()
+ {
+ $broadcaster = new FakeBroadcaster;
+
+ $broadcaster->channel('somechannel', function () {});
+ }
+
+ public function testCanRegisterChannelsWithOptions()
+ {
+ $broadcaster = new FakeBroadcaster;
+
+ $options = [ 'a' => [ 'b', 'c' ] ];
+ $broadcaster->channel('somechannel', function () {}, $options);
+
+ $this->assertEquals(
+ $options,
+ $broadcaster->retrieveChannelOptions('somechannel')
+ );
+ }
+
+ public function testRetrieveUserWithoutGuard()
+ {
+ $broadcaster = new FakeBroadcaster;
+
+ $broadcaster->channel('somechannel', function () {});
+
+ $request = m::mock(\Illuminate\Http\Request::class);
+ $request->shouldReceive('user')
+ ->once()
+ ->withNoArgs()
+ ->andReturn(new DummyUser);
+
+ $this->assertInstanceOf(
+ DummyUser::class,
+ $broadcaster->retrieveUser($request, 'somechannel')
+ );
+ }
+
+ public function testRetrieveUserWithOneGuardUsingAStringForSpecifyingGuard()
+ {
+ $broadcaster = new FakeBroadcaster;
+
+ $broadcaster->channel('somechannel', function () {}, ['guards' => 'myguard']);
+
+ $request = m::mock(\Illuminate\Http\Request::class);
+ $request->shouldReceive('user')
+ ->once()
+ ->with('myguard')
+ ->andReturn(new DummyUser);
+
+ $this->assertInstanceOf(
+ DummyUser::class,
+ $broadcaster->retrieveUser($request, 'somechannel')
+ );
+ }
+
+ public function testRetrieveUserWithMultipleGuardsAndRespectGuardsOrder()
+ {
+ $broadcaster = new FakeBroadcaster;
+
+ $broadcaster->channel('somechannel', function () {}, ['guards' => ['myguard1', 'myguard2']]);
+ $broadcaster->channel('someotherchannel', function () {}, ['guards' => ['myguard2', 'myguard1']]);
+
+
+ $request = m::mock(\Illuminate\Http\Request::class);
+ $request->shouldReceive('user')
+ ->once()
+ ->with('myguard1')
+ ->andReturn(null);
+ $request->shouldReceive('user')
+ ->twice()
+ ->with('myguard2')
+ ->andReturn(new DummyUser)
+ ->ordered('user');
+
+ $this->assertInstanceOf(
+ DummyUser::class,
+ $broadcaster->retrieveUser($request, 'somechannel')
+ );
+
+ $this->assertInstanceOf(
+ DummyUser::class,
+ $broadcaster->retrieveUser($request, 'someotherchannel')
+ );
+ }
+
+ public function testRetrieveUserDontUseDefaultGuardWhenOneGuardSpecified()
+ {
+ $broadcaster = new FakeBroadcaster;
+
+ $broadcaster->channel('somechannel', function () {}, ['guards' => 'myguard']);
+
+ $request = m::mock(\Illuminate\Http\Request::class);
+ $request->shouldReceive('user')
+ ->once()
+ ->with('myguard')
+ ->andReturn(null);
+ $request->shouldNotReceive('user')
+ ->withNoArgs();
+
+ $broadcaster->retrieveUser($request, 'somechannel');
+ }
+
+ public function testRetrieveUserDontUseDefaultGuardWhenMultipleGuardsSpecified()
+ {
+ $broadcaster = new FakeBroadcaster;
+
+ $broadcaster->channel('somechannel', function () {}, ['guards' => ['myguard1', 'myguard2']]);
+
+
+ $request = m::mock(\Illuminate\Http\Request::class);
+ $request->shouldReceive('user')
+ ->once()
+ ->with('myguard1')
+ ->andReturn(null);
+ $request->shouldReceive('user')
+ ->once()
+ ->with('myguard2')
+ ->andReturn(null);
+ $request->shouldNotReceive('user')
+ ->withNoArgs();
+
+ $broadcaster->retrieveUser($request, 'somechannel');
+ }
}
class FakeBroadcaster extends Broadcaster
@@ -114,6 +239,16 @@ public function extractAuthParameters($pattern, $channel, $callback)
{
return parent::extractAuthParameters($pattern, $channel, $callback);
}
+
+ public function retrieveChannelOptions($channel)
+ {
+ return parent::retrieveChannelOptions($channel);
+ }
+
+ public function retrieveUser($request, $channel)
+ {
+ return parent::retrieveUser($request, $channel);
+ }
}
class BroadcasterTestEloquentModelStub extends Model
@@ -163,3 +298,8 @@ public function join($user, BroadcasterTestEloquentModelStub $model, $nonModel)
//
}
}
+
+class DummyUser
+{
+
+}
src/Illuminate/Auth/composer.json+8 8
@@ -15,10 +15,10 @@
],
"require": {
"php": "^7.1.3",
- "illuminate/contracts": "5.7.*",
- "illuminate/http": "5.7.*",
- "illuminate/queue": "5.7.*",
- "illuminate/support": "5.7.*"
+ "illuminate/contracts": "5.8.*",
+ "illuminate/http": "5.8.*",
+ "illuminate/queue": "5.8.*",
+ "illuminate/support": "5.8.*"
},
"autoload": {
"psr-4": {
@@ -27,13 +27,13 @@
},
"extra": {
"branch-alias": {
- "dev-master": "5.7-dev"
+ "dev-master": "5.8-dev"
}
},
"suggest": {
- "illuminate/console": "Required to use the auth:clear-resets command (5.7.*).",
- "illuminate/queue": "Required to fire login / logout events (5.7.*).",
- "illuminate/session": "Required to use the session based guard (5.7.*)."
+ "illuminate/console": "Required to use the auth:clear-resets command (5.8.*).",
+ "illuminate/queue": "Required to fire login / logout events (5.8.*).",
+ "illuminate/session": "Required to use the session based guard (5.8.*)."
},
"config": {
"sort-packages": true
src/Illuminate/Database/composer.json+8 8
@@ -16,9 +16,9 @@
],
"require": {
"php": "^7.1.3",
- "illuminate/container": "5.7.*",
- "illuminate/contracts": "5.7.*",
- "illuminate/support": "5.7.*"
+ "illuminate/container": "5.8.*",
+ "illuminate/contracts": "5.8.*",
+ "illuminate/support": "5.8.*"
},
"autoload": {
"psr-4": {
@@ -27,16 +27,16 @@
},
"extra": {
"branch-alias": {
- "dev-master": "5.7-dev"
+ "dev-master": "5.8-dev"
}
},
"suggest": {
"doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6).",
"fzaninotto/faker": "Required to use the eloquent factory builder (^1.4).",
- "illuminate/console": "Required to use the database commands (5.7.*).",
- "illuminate/events": "Required to use the observers with Eloquent (5.7.*).",
- "illuminate/filesystem": "Required to use the migrations (5.7.*).",
- "illuminate/pagination": "Required to paginate the result set (5.7.*)."
+ "illuminate/console": "Required to use the database commands (5.8.*).",
+ "illuminate/events": "Required to use the observers with Eloquent (5.8.*).",
+ "illuminate/filesystem": "Required to use the migrations (5.8.*).",
+ "illuminate/pagination": "Required to paginate the result set (5.8.*)."
},
"config": {
"sort-packages": true
src/Illuminate/Queue/composer.json+8 8
@@ -15,12 +15,12 @@
],
"require": {
"php": "^7.1.3",
- "illuminate/console": "5.7.*",
- "illuminate/container": "5.7.*",
- "illuminate/contracts": "5.7.*",
- "illuminate/database": "5.7.*",
- "illuminate/filesystem": "5.7.*",
- "illuminate/support": "5.7.*",
+ "illuminate/console": "5.8.*",
+ "illuminate/container": "5.8.*",
+ "illuminate/contracts": "5.8.*",
+ "illuminate/database": "5.8.*",
+ "illuminate/filesystem": "5.8.*",
+ "illuminate/support": "5.8.*",
"symfony/debug": "^4.1",
"symfony/process": "^4.1"
},
@@ -31,14 +31,14 @@
},
"extra": {
"branch-alias": {
- "dev-master": "5.7-dev"
+ "dev-master": "5.8-dev"
}
},
"suggest": {
"ext-pcntl": "Required to use all features of the queue worker.",
"ext-posix": "Required to use all features of the queue worker.",
"aws/aws-sdk-php": "Required to use the SQS queue driver (^3.0).",
- "illuminate/redis": "Required to use the Redis queue driver (5.7.*).",
+ "illuminate/redis": "Required to use the Redis queue driver (5.8.*).",
"pda/pheanstalk": "Required to use the Beanstalk queue driver (^3.0)."
},
"config": {
src/Illuminate/Routing/composer.json+8 8
@@ -15,12 +15,12 @@
],
"require": {
"php": "^7.1.3",
- "illuminate/container": "5.7.*",
- "illuminate/contracts": "5.7.*",
- "illuminate/http": "5.7.*",
- "illuminate/pipeline": "5.7.*",
- "illuminate/session": "5.7.*",
- "illuminate/support": "5.7.*",
+ "illuminate/container": "5.8.*",
+ "illuminate/contracts": "5.8.*",
+ "illuminate/http": "5.8.*",
+ "illuminate/pipeline": "5.8.*",
+ "illuminate/session": "5.8.*",
+ "illuminate/support": "5.8.*",
"symfony/debug": "^4.1",
"symfony/http-foundation": "^4.1",
"symfony/http-kernel": "^4.1",
@@ -33,11 +33,11 @@
},
"extra": {
"branch-alias": {
- "dev-master": "5.7-dev"
+ "dev-master": "5.8-dev"
}
},
"suggest": {
- "illuminate/console": "Required to use the make commands (5.7.*).",
+ "illuminate/console": "Required to use the make commands (5.8.*).",
"symfony/psr-http-message-bridge": "Required to psr7 bridging features (^1.0)."
},
"config": {
src/Illuminate/Cache/composer.json+6 6
@@ -15,8 +15,8 @@
],
"require": {
"php": "^7.1.3",
- "illuminate/contracts": "5.7.*",
- "illuminate/support": "5.7.*"
+ "illuminate/contracts": "5.8.*",
+ "illuminate/support": "5.8.*"
},
"autoload": {
"psr-4": {
@@ -25,13 +25,13 @@
},
"extra": {
"branch-alias": {
- "dev-master": "5.7-dev"
+ "dev-master": "5.8-dev"
}
},
"suggest": {
- "illuminate/database": "Required to use the database cache driver (5.7.*).",
- "illuminate/filesystem": "Required to use the file cache driver (5.7.*).",
- "illuminate/redis": "Required to use the redis cache driver (5.7.*)."
+ "illuminate/database": "Required to use the database cache driver (5.8.*).",
+ "illuminate/filesystem": "Required to use the file cache driver (5.8.*).",
+ "illuminate/redis": "Required to use the redis cache driver (5.8.*)."
},
"config": {
"sort-packages": true
src/Illuminate/Validation/composer.json+6 6
@@ -15,10 +15,10 @@
],
"require": {
"php": "^7.1.3",
- "illuminate/container": "5.7.*",
- "illuminate/contracts": "5.7.*",
- "illuminate/support": "5.7.*",
- "illuminate/translation": "5.7.*",
+ "illuminate/container": "5.8.*",
+ "illuminate/contracts": "5.8.*",
+ "illuminate/support": "5.8.*",
+ "illuminate/translation": "5.8.*",
"symfony/http-foundation": "^4.1"
},
"autoload": {
@@ -28,11 +28,11 @@
},
"extra": {
"branch-alias": {
- "dev-master": "5.7-dev"
+ "dev-master": "5.8-dev"
}
},
"suggest": {
- "illuminate/database": "Required to use the database presence verifier (5.7.*)."
+ "illuminate/database": "Required to use the database presence verifier (5.8.*)."
},
"config": {
"sort-packages": true
src/Illuminate/View/composer.json+6 6
@@ -15,11 +15,11 @@
],
"require": {
"php": "^7.1.3",
- "illuminate/container": "5.7.*",
- "illuminate/contracts": "5.7.*",
- "illuminate/events": "5.7.*",
- "illuminate/filesystem": "5.7.*",
- "illuminate/support": "5.7.*",
+ "illuminate/container": "5.8.*",
+ "illuminate/contracts": "5.8.*",
+ "illuminate/events": "5.8.*",
+ "illuminate/filesystem": "5.8.*",
+ "illuminate/support": "5.8.*",
"symfony/debug": "^4.1"
},
"autoload": {
@@ -29,7 +29,7 @@
},
"extra": {
"branch-alias": {
- "dev-master": "5.7-dev"
+ "dev-master": "5.8-dev"
}
},
"config": {
tests/Integration/Database/EloquentMorphToLazyEagerLoadingTest.php+94 0
@@ -0,0 +1,94 @@
+<?php
+
+namespace Illuminate\Tests\Integration\Database\EloquentMorphToLazyEagerLoadingTest;
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Tests\Integration\Database\DatabaseTestCase;
+
+/**
+ * @group integration
+ */
+class EloquentMorphToLazyEagerLoadingTest extends DatabaseTestCase
+{
+ public function setUp()
+ {
+ parent::setUp();
+
+ Schema::create('users', function (Blueprint $table) {
+ $table->increments('id');
+ });
+
+ Schema::create('posts', function (Blueprint $table) {
+ $table->increments('post_id');
+ $table->unsignedInteger('user_id');
+ });
+
+ Schema::create('videos', function (Blueprint $table) {
+ $table->increments('video_id');
+ });
+
+ Schema::create('comments', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('commentable_type');
+ $table->integer('commentable_id');
+ });
+
+ $user = User::create();
+
+ $post = tap((new Post)->user()->associate($user))->save();
+
+ $video = Video::create();
+
+ (new Comment)->commentable()->associate($post)->save();
+ (new Comment)->commentable()->associate($video)->save();
+ }
+
+ public function test_lazy_eager_loading()
+ {
+ $comments = Comment::all();
+
+ \DB::enableQueryLog();
+
+ $comments->load('commentable');
+
+ $this->assertCount(3, \DB::getQueryLog());
+ $this->assertTrue($comments[0]->relationLoaded('commentable'));
+ $this->assertTrue($comments[0]->commentable->relationLoaded('user'));
+ $this->assertTrue($comments[1]->relationLoaded('commentable'));
+ }
+}
+
+class Comment extends Model
+{
+ public $timestamps = false;
+
+ public function commentable()
+ {
+ return $this->morphTo();
+ }
+}
+
+class Post extends Model
+{
+ public $timestamps = false;
+ protected $primaryKey = 'post_id';
+ protected $with = ['user'];
+
+ public function user()
+ {
+ return $this->belongsTo(User::class);
+ }
+}
+
+class User extends Model
+{
+ public $timestamps = false;
+}
+
+class Video extends Model
+{
+ public $timestamps = false;
+ protected $primaryKey = 'video_id';
+}
src/Illuminate/Database/DetectsLostConnections.php | 1 +
1 file changed, 1 insertion(+)
tests/Integration/Database/EloquentBelongsToManyTest.php+24 0
@@ -583,6 +583,24 @@ public function can_touch_related_models()
$this->assertNotEquals('2017-10-10 10:10:10', Tag::find(300)->updated_at);
}
+
+ public function test_custom_related_key()
+ {
+ $post = Post::create(['title' => str_random()]);
+
+ $tag = $post->tagsWithCustomRelatedKey()->create(['name' => str_random()]);
+ $this->assertEquals($tag->name, $post->tagsWithCustomRelatedKey()->first()->pivot->tag_id);
+
+ $post->tagsWithCustomRelatedKey()->detach($tag);
+
+ $post->tagsWithCustomRelatedKey()->attach($tag);
+ $this->assertEquals($tag->name, $post->tagsWithCustomRelatedKey()->first()->pivot->tag_id);
+
+ $post->tagsWithCustomRelatedKey()->detach(new Collection([$tag]));
+
+ $post->tagsWithCustomRelatedKey()->attach(new Collection([$tag]));
+ $this->assertEquals($tag->name, $post->tagsWithCustomRelatedKey()->first()->pivot->tag_id);
+ }
}
class Post extends Model
@@ -619,6 +637,12 @@ public function tagsWithCustomAccessor()
->using(CustomPivot::class)
->as('tag');
}
+
+ public function tagsWithCustomRelatedKey()
+ {
+ return $this->belongsToMany(Tag::class, 'posts_tags', 'post_id', 'tag_id', 'id', 'name')
+ ->withPivot('flag');
+ }
}
class Tag extends Model
handling
composer.json | 2 +-
src/Illuminate/Console/Scheduling/Event.php | 3 +-
.../Console/Scheduling/ScheduleRunCommand.php | 4 +-
.../Eloquent/Concerns/HasAttributes.php | 14 +-
.../Eloquent/Concerns/HasTimestamps.php | 4 +-
.../Exceptions/MaintenanceModeException.php | 5 +-
.../Foundation/Testing/TestCase.php | 6 +-
src/Illuminate/Foundation/helpers.php | 6 +-
.../Failed/DatabaseFailedJobProvider.php | 4 +-
.../Session/Middleware/StartSession.php | 5 +-
src/Illuminate/Support/Facades/Date.php | 178 ++++++++++++++++++
src/Illuminate/Support/InteractsWithTime.php | 2 +-
src/Illuminate/Support/composer.json | 2 +-
.../Concerns/ValidatesAttributes.php | 3 +-
.../Mail/SendingMailWithLocaleTest.php | 2 +-
.../SendingNotificationsWithLocaleTest.php | 2 +-
tests/Support/DateFacadeTest.php | 107 +++++++++++
tests/Support/fixtures/CustomDateClass.php | 21 +++
18 files changed, 343 insertions(+), 27 deletions(-)
create mode 100644 src/Illuminate/Support/Facades/Date.php
create mode 100644 tests/Support/DateFacadeTest.php
create mode 100644 tests/Support/fixtures/CustomDateClass.php
src/Illuminate/Broadcasting/composer.json+5 5
@@ -16,10 +16,10 @@
"require": {
"php": "^7.1.3",
"psr/log": "^1.0",
- "illuminate/bus": "5.7.*",
- "illuminate/contracts": "5.7.*",
- "illuminate/queue": "5.7.*",
- "illuminate/support": "5.7.*"
+ "illuminate/bus": "5.8.*",
+ "illuminate/contracts": "5.8.*",
+ "illuminate/queue": "5.8.*",
+ "illuminate/support": "5.8.*"
},
"autoload": {
"psr-4": {
@@ -28,7 +28,7 @@
},
"extra": {
"branch-alias": {
- "dev-master": "5.7-dev"
+ "dev-master": "5.8-dev"
}
},
"suggest": {
More files changed — see the full commit.

References