Browse Source

Merge branch '4.next' into 5.x

Corey Taylor 4 years ago
parent
commit
2918a44d1d

+ 13 - 0
.github/workflows/cancel.yml

@@ -0,0 +1,13 @@
+name: Cancel
+on:
+  workflow_run:
+    workflows: ["CI"]
+    types:
+      - requested
+jobs:
+  cancel:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: styfle/cancel-workflow-action@0.9.1
+      with:
+        workflow_id: ${{ github.event.workflow.id }}

+ 10 - 1
src/Http/Middleware/CsrfProtectionMiddleware.php

@@ -23,6 +23,7 @@ use Cake\Http\Exception\InvalidCsrfTokenException;
 use Cake\Http\Response;
 use Cake\Utility\Hash;
 use Cake\Utility\Security;
+use InvalidArgumentException;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\MiddlewareInterface;
@@ -144,7 +145,11 @@ class CsrfProtectionMiddleware implements MiddlewareInterface
         $cookieData = Hash::get($cookies, $this->_config['cookieName']);
 
         if (is_string($cookieData) && $cookieData !== '') {
-            $request = $request->withAttribute('csrfToken', $this->saltToken($cookieData));
+            try {
+                $request = $request->withAttribute('csrfToken', $this->saltToken($cookieData));
+            } catch (InvalidArgumentException $e) {
+                $cookieData = null;
+            }
         }
 
         if ($method === 'GET' && $cookieData === null) {
@@ -241,6 +246,10 @@ class CsrfProtectionMiddleware implements MiddlewareInterface
             return $token;
         }
         $decoded = base64_decode($token, true);
+        if ($decoded === false) {
+            throw new InvalidArgumentException('Invalid token data.');
+        }
+
         $length = strlen($decoded);
         $salt = Security::randomBytes($length);
         $salted = '';

+ 97 - 5
src/TestSuite/Fixture/FixtureHelper.php

@@ -17,9 +17,14 @@ declare(strict_types=1);
 namespace Cake\TestSuite\Fixture;
 
 use Cake\Core\Configure;
+use Cake\Database\Connection;
 use Cake\Database\Driver\Postgres;
+use Cake\Database\Driver\Sqlite;
+use Cake\Database\Driver\Sqlserver;
+use Cake\Database\Schema\TableSchema;
 use Cake\Datasource\ConnectionInterface;
 use Cake\Datasource\ConnectionManager;
+use Cake\Datasource\FixtureInterface;
 use Closure;
 use UnexpectedValueException;
 
@@ -127,8 +132,16 @@ class FixtureHelper
      */
     public function insert(array $fixtures): void
     {
-        $this->runPerConnection(function (ConnectionInterface $connection, array $groupFixtures): void {
-            if (
+        $this->runPerConnection(function (ConnectionInterface $connection, array $groupFixtures) {
+            if ($connection instanceof Connection) {
+                $sortedFixtures = $this->sortByConstraint($connection, $groupFixtures);
+            }
+
+            if (isset($sortedFixtures)) {
+                foreach ($sortedFixtures as $fixture) {
+                    $fixture->insert($connection);
+                }
+            } elseif (
                 method_exists($connection, 'transactional') &&
                 method_exists($connection, 'disableConstraints') &&
                 $connection->getDriver() instanceof Postgres
@@ -164,9 +177,25 @@ class FixtureHelper
      */
     public function truncate(array $fixtures): void
     {
-        $this->runPerConnection(function (ConnectionInterface $connection, array $groupFixtures): void {
-            if (method_exists($connection, 'disableConstraints')) {
-                $connection->disableConstraints(function () use ($connection, $groupFixtures): void {
+        $this->runPerConnection(function (ConnectionInterface $connection, array $groupFixtures) {
+            if ($connection instanceof Connection) {
+                $sortedFixtures = $this->sortByConstraint($connection, $groupFixtures);
+            }
+
+            $driver = $connection->getDriver();
+            if (
+                isset($sortedFixtures) &&
+                (
+                    $driver instanceof Postgres ||
+                    $driver instanceof Sqlite ||
+                    $driver instanceof Sqlserver
+                )
+            ) {
+                foreach (array_reverse($sortedFixtures) as $fixture) {
+                    $fixture->truncate($connection);
+                }
+            } elseif (method_exists($connection, 'disableConstraints')) {
+                $connection->disableConstraints(function () use ($connection, $groupFixtures) {
                     foreach ($groupFixtures as $fixture) {
                         $fixture->truncate($connection);
                     }
@@ -178,4 +207,67 @@ class FixtureHelper
             }
         }, $fixtures);
     }
+
+    /**
+     * Sort fixtures with foreign constraints last if possible, otherwise returns null.
+     *
+     * @param \Cake\Database\Connection $connection Database connection
+     * @param array<\Cake\Datasource\FixtureInterface> $fixtures Database fixtures
+     * @return array|null
+     */
+    protected function sortByConstraint(Connection $connection, array $fixtures): ?array
+    {
+        $constrained = [];
+        $unconstrained = [];
+        foreach ($fixtures as $fixture) {
+            $references = $this->getForeignReferences($connection, $fixture);
+            if ($references) {
+                $constrained[$fixture->sourceName()] = ['references' => $references, 'fixture' => $fixture];
+            } else {
+                $unconstrained[] = $fixture;
+            }
+        }
+
+        // Check if any fixtures reference another fixture with constrants
+        // If they do, then there might be cross-dependencies which we don't support sorting
+        foreach ($constrained as ['references' => $references]) {
+            foreach ($references as $reference) {
+                if (isset($constrained[$reference])) {
+                    return null;
+                }
+            }
+        }
+
+        return array_merge($unconstrained, array_column($constrained, 'fixture'));
+    }
+
+    /**
+     * Gets array of foreign references for fixtures table.
+     *
+     * @param \Cake\Database\Connection $connection Database connection
+     * @param \Cake\Datasource\FixtureInterface $fixture Database fixture
+     * @return array
+     */
+    protected function getForeignReferences(Connection $connection, FixtureInterface $fixture): array
+    {
+        static $schemas = [];
+
+        // Get and cache off the schema since TestFixture generates a fake schema based on $fields
+        $tableName = $fixture->sourceName();
+        if (!isset($schemas[$tableName])) {
+            $schemas[$tableName] = $connection->getSchemaCollection()->describe($tableName);
+        }
+        $schema = $schemas[$tableName];
+
+        $references = [];
+        foreach ($schema->constraints() as $constraintName) {
+            $constraint = $schema->getConstraint($constraintName);
+
+            if ($constraint && $constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
+                $references[] = $constraint['references'][0];
+            }
+        }
+
+        return $references;
+    }
 }

+ 1 - 1
tests/TestCase/Command/SchemaCacheCommandsTest.php

@@ -68,9 +68,9 @@ class SchemaCacheCommandsTest extends TestCase
      */
     public function tearDown(): void
     {
+        $this->connection->cacheMetadata(false);
         parent::tearDown();
 
-        $this->connection->cacheMetadata('_cake_model_');
         unset($this->connection);
         Cache::drop('orm_cache');
     }

+ 10 - 1
tests/TestCase/Controller/ComponentTest.php

@@ -251,7 +251,7 @@ class ComponentTest extends TestCase
     /**
      * Tests deprecated shutdown callback
      */
-    public function testEventShutdown(): void
+    public function testDeprecatedEventShutdown(): void
     {
         $Collection = new ComponentRegistry();
 
@@ -270,6 +270,15 @@ class ComponentTest extends TestCase
         });
     }
 
+    public function testDeprecatedEventShutdownPath(): void
+    {
+        $this->expectDeprecation();
+        $this->expectDeprecationMessage('Component.php');
+
+        $component = new TestShutdownComponent(new ComponentRegistry());
+        $result = $component->__debugInfo();
+    }
+
     /**
      * Test that calling getController() without setting a controller throws exception
      */

+ 1 - 1
tests/TestCase/Database/Schema/CollectionTest.php

@@ -55,8 +55,8 @@ class CollectionTest extends TestCase
      */
     public function tearDown(): void
     {
-        parent::tearDown();
         $this->connection->cacheMetadata(false);
+        parent::tearDown();
         unset($this->connection);
     }
 

+ 1 - 1
tests/TestCase/Database/SchemaCacheTest.php

@@ -65,9 +65,9 @@ class SchemaCacheTest extends TestCase
      */
     public function tearDown(): void
     {
+        $this->connection->cacheMetadata(false);
         parent::tearDown();
 
-        $this->connection->cacheMetadata(false);
         unset($this->connection);
         Cache::drop('orm_cache');
     }

+ 40 - 0
tests/TestCase/Http/Middleware/CsrfProtectionMiddlewareTest.php

@@ -165,6 +165,27 @@ class CsrfProtectionMiddlewareTest extends TestCase
     }
 
     /**
+     * Test that the CSRF tokens are regenerated when token is not valid
+     *
+     * @return void
+     */
+    public function testRegenerateTokenOnGetWithInvalidData()
+    {
+        $request = new ServerRequest([
+            'environment' => [
+                'REQUEST_METHOD' => 'GET',
+            ],
+            'cookies' => ['csrfToken' => "\x20\x26"],
+        ]);
+
+        $middleware = new CsrfProtectionMiddleware();
+        /** @var \Cake\Http\Response $response */
+        $response = $middleware->process($request, $this->_getRequestHandler());
+        $this->assertInstanceOf(Response::class, $response);
+        $this->assertGreaterThan(32, strlen($response->getCookie('csrfToken')['value']));
+    }
+
+    /**
      * Test that the CSRF tokens are set for redirect responses
      */
     public function testRedirectResponseCookies(): void
@@ -356,6 +377,25 @@ class CsrfProtectionMiddlewareTest extends TestCase
     }
 
     /**
+     * Test that invalid string cookies are rejected.
+     *
+     * @return void
+     */
+    public function testInvalidTokenStringCookies()
+    {
+        $this->expectException(InvalidCsrfTokenException::class);
+        $request = new ServerRequest([
+            'environment' => [
+                'REQUEST_METHOD' => 'POST',
+            ],
+            'post' => ['_csrfToken' => ["\x20\x26"]],
+            'cookies' => ['csrfToken' => ["\x20\x26"]],
+        ]);
+        $middleware = new CsrfProtectionMiddleware();
+        $middleware->process($request, $this->_getRequestHandler());
+    }
+
+    /**
      * Test that request non string cookies are ignored.
      */
     public function testInvalidTokenNonStringCookies(): void

+ 0 - 3
tests/TestCase/Http/Session/DatabaseSessionTest.php

@@ -48,9 +48,6 @@ class DatabaseSessionTest extends TestCase
         parent::setUp();
         static::setAppNamespace();
         $this->storage = new DatabaseSession();
-
-        // With metadata caching on SQLServer/windows tests fail.
-        ConnectionManager::get('test')->cacheMetadata(false);
     }
 
     /**

+ 2 - 1
tests/TestCase/ORM/ColumnSchemaAwareTypeIntegrationTest.php

@@ -22,12 +22,13 @@ class ColumnSchemaAwareTypeIntegrationTest extends TestCase
     public function setUp(): void
     {
         parent::setUp();
-        $this->markTestSkipped('This test requires non-auto-fixtures');
 
         $this->textType = TypeFactory::build('text');
         TypeFactory::map('text', ColumnSchemaAwareType::class);
         // For SQLServer.
         TypeFactory::map('nvarchar', ColumnSchemaAwareType::class);
+
+        $this->markTestSkipped('This test requires non-auto-fixtures');
     }
 
     public function tearDown(): void