diff --git a/docker/Yii2Oauth2Server/Dockerfile b/docker/Yii2Oauth2Server/Dockerfile index a919572..79f690a 100644 --- a/docker/Yii2Oauth2Server/Dockerfile +++ b/docker/Yii2Oauth2Server/Dockerfile @@ -3,6 +3,8 @@ FROM yiisoftware/yii2-php:7.4-apache ARG APP_DIR=/app RUN echo "APP_DIR: ${APP_DIR}" +RUN echo "deb http://ftp.de.debian.org/debian buster main" >> /etc/apt/sources.list + RUN apt-get update #region Not required but often used for debugging @@ -14,6 +16,11 @@ COPY docker/Yii2Oauth2Server/apache/vhost.conf /etc/apache2/sites-available/000- # Create alias for PHP with Xdebug RUN echo 'alias phpx="php -d xdebug.start_with_request=1"' >> ~/.bashrc +# Enable pdo_postgres +RUN apt-get install -y libpq-dev +RUN docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql +RUN docker-php-ext-install pdo pdo_pgsql pgsql + # Copy the app directory. NOTE! For development docker-compose will overwrite this directory with a mount COPY . ${APP_DIR} diff --git a/src/components/repositories/Oauth2ScopeRepository.php b/src/components/repositories/Oauth2ScopeRepository.php index 8b225d4..e43b6b1 100644 --- a/src/components/repositories/Oauth2ScopeRepository.php +++ b/src/components/repositories/Oauth2ScopeRepository.php @@ -83,6 +83,7 @@ public function finalizeScopes( 'user_client_scope.client_id' => $client->getPrimaryKey(), 'user_client_scope.enabled' => 1, ]) + ->orderBy('id') ->indexBy('id') ->all(); diff --git a/src/migrations/Oauth2_00001_CreateOauth2TablesMigration.php b/src/migrations/Oauth2_00001_CreateOauth2TablesMigration.php index 80ec36d..da22cac 100644 --- a/src/migrations/Oauth2_00001_CreateOauth2TablesMigration.php +++ b/src/migrations/Oauth2_00001_CreateOauth2TablesMigration.php @@ -18,6 +18,7 @@ use rhertogh\Yii2Oauth2Server\models\Oauth2Scope; use rhertogh\Yii2Oauth2Server\Oauth2Module; use yii\base\InvalidConfigException; +use yii\db\ColumnSchemaBuilder; use yii\db\Schema; /** @@ -46,7 +47,37 @@ public static function generationIsActive($module) public function safeUp() { foreach ($this->getTables() as $table => $definition) { - $this->createTable($table, $definition); + $this->createTable($table, $definition['table']); + $rawTableName = $this->getDb()->getSchema()->getRawTableName($table); + if (!empty($definition['primaryKey'])) { + $this->addPrimaryKey( + $rawTableName . '_pk', + $table, + $definition['primaryKey']['columns'] + ); + } + if (!empty($definition['indexes'])) { + foreach ($definition['indexes'] as $index) { + $this->createIndex( + $rawTableName . '_' . $index['name'] . '_index', + $table, + $index['columns'], + $index['unique']); + } + } + if (!empty($definition['foreignKeys'])) { + foreach ($definition['foreignKeys'] as $foreignKey) { + $this->addForeignKey( + $rawTableName . '_' . $foreignKey['name'] . '_fk', + $table, + $foreignKey['columns'], + $foreignKey['refTable'], + $foreignKey['refColumns'], + $foreignKey['delete'], + $foreignKey['update'], + ); + } + } } } @@ -98,190 +129,471 @@ protected function getTables() $userPkSchema = null; } + if ($userPkSchema) { + /** @var ColumnSchemaBuilder $userPkSchemaColumnBuilder */ + $userPkSchemaColumnBuilder = $this->{$userPkSchema->type}(); + } else { + $userPkSchemaColumnBuilder = $this->string(); + } + // See https://datatracker.ietf.org/doc/html/rfc7591#section-2 // (although not yet fully implemented, some fields follow this standard). $tables = [ $clientTable => [ - 'id' => $this->primaryKey(), - 'identifier' => $this->string()->notNull()->unique(), - 'name' => $this->string()->notNull(), - 'type' => $this->boolean()->notNull()->defaultValue(Oauth2ClientInterface::TYPE_CONFIDENTIAL), - 'secret' => $this->text(), - 'old_secret' => $this->text(), - 'old_secret_valid_until' => $this->dateTime(), - 'logo_uri' => $this->string(), - 'tos_uri' => $this->string(), - 'contacts' => $this->text() - ->comment('JSON encoded array of strings with contact details for the client.'), - 'redirect_uris' => $this->json(), - 'token_types' => $this->integer()->notNull()->defaultValue(Oauth2AccessToken::TYPE_BEARER), - 'grant_types' => $this->integer()->notNull()->defaultValue(Oauth2Module::GRANT_TYPE_AUTH_CODE | Oauth2Module::GRANT_TYPE_REFRESH_TOKEN), - 'scope_access' => $this->integer()->notNull()->defaultValue(Oauth2Client::SCOPE_ACCESS_STRICT) - ->comment('Determines how strict scopes must be defined for this client.'), - 'user_account_selection' => $this->integer()->comment('Determines when to show user account selection screen. Using Oauth2Module::$defaultUserAccountSelection when `null`.'), - 'allow_auth_code_without_pkce' => $this->boolean()->notNull()->defaultValue(0) - ->comment('Require clients to use PKCE when using the auth_code grant type.'), - 'skip_authorization_if_scope_is_allowed' => $this->boolean()->notNull()->defaultValue(0) - ->comment('Skip user authorization of client if there are no scopes that require authorization.'), - 'client_credentials_grant_user_id' => ($userTable ? $userPkSchema->dbType : Schema::TYPE_STRING) - . ' COMMENT "Optional user id to use in case of grant type \'client_credentials\'.' - . ' This user account should also be connected to the client via the `' . $userClientTable . '` table and, if applicable, the `' . $userClientScopeTable . '` table."', - 'oidc_allow_offline_access_without_consent' => $this->boolean()->notNull()->defaultValue(0) - ->comment('Allow the OpenID Connect "offline_access" scope for this client without the "prompt" parameter contains "consent".'), - 'oidc_userinfo_encrypted_response_alg' => $this->string(), - 'enabled' => $this->boolean()->notNull()->defaultValue(1), - 'created_at' => $this->integer()->notNull(), - 'updated_at' => $this->integer()->notNull(), - - ...( - $userTable - ? ['FOREIGN KEY (client_credentials_grant_user_id) REFERENCES ' . $userTable . ' (' . $userPkColumn . ') ON DELETE RESTRICT ON UPDATE CASCADE'] - : ['KEY (client_credentials_grant_user_id)'] - ), - 'KEY (token_types)', - 'KEY (grant_types)', - 'KEY (enabled)', + 'table' => [ + 'id' => $this->primaryKey(), + 'identifier' => $this->string()->notNull()->unique(), + 'name' => $this->string()->notNull(), + 'type' => $this->integer()->notNull()->defaultValue(Oauth2ClientInterface::TYPE_CONFIDENTIAL), + 'secret' => $this->text(), + 'old_secret' => $this->text(), + 'old_secret_valid_until' => $this->dateTime(), + 'logo_uri' => $this->string(), + 'tos_uri' => $this->string(), + 'contacts' => $this->text() + ->comment('JSON encoded array of strings with contact details for the client.'), + 'redirect_uris' => $this->json(), + 'token_types' => $this->integer()->notNull()->defaultValue(Oauth2AccessToken::TYPE_BEARER), + 'grant_types' => $this->integer()->notNull()->defaultValue(Oauth2Module::GRANT_TYPE_AUTH_CODE | Oauth2Module::GRANT_TYPE_REFRESH_TOKEN), + 'scope_access' => $this->integer()->notNull()->defaultValue(Oauth2Client::SCOPE_ACCESS_STRICT) + ->comment('Determines how strict scopes must be defined for this client.'), + 'user_account_selection' => $this->integer()->comment('Determines when to show user account selection screen. Using Oauth2Module::$defaultUserAccountSelection when `null`.'), + 'allow_auth_code_without_pkce' => $this->boolean()->notNull()->defaultValue(false) + ->comment('Require clients to use PKCE when using the auth_code grant type.'), + 'skip_authorization_if_scope_is_allowed' => $this->boolean()->notNull()->defaultValue(false) + ->comment('Skip user authorization of client if there are no scopes that require authorization.'), + 'client_credentials_grant_user_id' => (clone $userPkSchemaColumnBuilder) + ->comment("Optional user id to use in case of grant type 'client_credentials'." + . " This user account should also be connected to the client via the `$userClientTable` table and, if applicable, the `$userClientScopeTable` table."), + 'oidc_allow_offline_access_without_consent' => $this->boolean()->notNull()->defaultValue(false) + ->comment('Allow the OpenID Connect "offline_access" scope for this client without the "prompt" parameter contains "consent".'), + 'oidc_userinfo_encrypted_response_alg' => $this->string(), + 'enabled' => $this->boolean()->notNull()->defaultValue(true), + 'created_at' => $this->integer()->notNull(), + 'updated_at' => $this->integer()->notNull(), + ], + 'foreignKeys' => [ + ...( + $userTable + ? [ + [ + 'name' => 'client_credentials_grant_user_id', + 'columns' => ['client_credentials_grant_user_id'], + 'refTable' => $userTable, + 'refColumns' => $userPkColumn, + 'delete' => static::RESTRICT, + 'update' => static::CASCADE, + ], + ] + : [] + ), + ], + 'indexes' => [ + ...( + !$userTable + ? [ + [ + 'name' => 'client_credentials_grant_user_id', + 'columns' => ['client_credentials_grant_user_id'], + 'unique' => false, + ], + ] + : [] + ), + [ + 'name' => 'token_types', + 'columns' => ['token_types'], + 'unique' => false, + ], + [ + 'name' => 'grant_types', + 'columns' => ['grant_types'], + 'unique' => false, + ], + [ + 'name' => 'enabled', + 'columns' => ['enabled'], + 'unique' => false, + ], + ], ], $scopeTable => [ - 'id' => $this->primaryKey(), - 'identifier' => $this->string()->notNull()->unique(), - 'description' => $this->text(), - 'authorization_message' => $this->text(), - 'applied_by_default' => $this->string()->notNull()->defaultValue(Oauth2Scope::APPLIED_BY_DEFAULT_NO), - 'required_on_authorization' => $this->boolean()->notNull()->defaultValue(1), - 'enabled' => $this->boolean()->notNull()->defaultValue(1), - 'created_at' => $this->integer()->notNull(), - 'updated_at' => $this->integer()->notNull(), - - 'KEY (applied_by_default)', - 'KEY (enabled)', + 'table' => [ + 'id' => $this->primaryKey(), + 'identifier' => $this->string()->notNull()->unique(), + 'description' => $this->text(), + 'authorization_message' => $this->text(), + 'applied_by_default' => $this->integer()->notNull()->defaultValue(Oauth2Scope::APPLIED_BY_DEFAULT_NO), + 'required_on_authorization' => $this->boolean()->notNull()->defaultValue(true), + 'enabled' => $this->boolean()->notNull()->defaultValue(true), + 'created_at' => $this->integer()->notNull(), + 'updated_at' => $this->integer()->notNull(), + ], + 'indexes' => [ + [ + 'name' => 'applied_by_default', + 'columns' => ['applied_by_default'], + 'unique' => false, + ], + [ + 'name' => 'enabled', + 'columns' => ['enabled'], + 'unique' => false, + ], + ], ], $clientScopeTable => [ - //'id' => $this->primaryKey(), - 'client_id' => $this->integer()->notNull(), - 'scope_id' => $this->integer()->notNull(), - 'applied_by_default' => $this->integer(), - 'required_on_authorization' => $this->boolean(), - 'enabled' => $this->boolean()->notNull()->defaultValue(1), - 'created_at' => $this->integer()->notNull(), - 'updated_at' => $this->integer()->notNull(), - 'PRIMARY KEY (client_id, scope_id)', - 'FOREIGN KEY (client_id) REFERENCES ' . $clientTable . ' (id) ON DELETE CASCADE ON UPDATE CASCADE', - 'FOREIGN KEY (scope_id) REFERENCES ' . $scopeTable . ' (id) ON DELETE CASCADE ON UPDATE CASCADE', - 'KEY (enabled)', - 'KEY (applied_by_default)', + 'table' => [ + 'client_id' => $this->integer()->notNull(), + 'scope_id' => $this->integer()->notNull(), + 'applied_by_default' => $this->integer(), + 'required_on_authorization' => $this->boolean(), + 'enabled' => $this->boolean()->notNull()->defaultValue(true), + 'created_at' => $this->integer()->notNull(), + 'updated_at' => $this->integer()->notNull(), + ], + 'primaryKey' => [ + 'columns' => ['client_id', 'scope_id'], + ], + 'foreignKeys' => [ + [ + 'name' => 'client_id', + 'columns' => ['client_id'], + 'refTable' => $clientTable, + 'refColumns' => ['id'], + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + [ + 'name' => 'scope_id', + 'columns' => ['scope_id'], + 'refTable' => $scopeTable, + 'refColumns' => ['id'], + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + ], + 'indexes' => [ + [ + 'name' => 'applied_by_default', + 'columns' => ['applied_by_default'], + 'unique' => false, + ], + [ + 'name' => 'enabled', + 'columns' => ['enabled'], + 'unique' => false, + ], + ], ], $authCodeTable => [ - 'id' => $this->bigPrimaryKey()->unsigned(), - 'identifier' => $this->string()->notNull()->unique(), - 'redirect_uri' => $this->string(), - 'expiry_date_time' => $this->dateTime()->notNull(), - 'client_id' => $this->integer()->notNull(), - 'user_id' => ($userTable ? $userPkSchema->dbType : Schema::TYPE_STRING) . ' NOT NULL', - 'enabled' => $this->boolean()->notNull()->defaultValue(1), // ToDo: do we need this ??? - 'created_at' => $this->integer()->notNull(), - 'updated_at' => $this->integer()->notNull(), - - 'FOREIGN KEY (client_id) REFERENCES ' . $clientTable . ' (id) ON DELETE CASCADE ON UPDATE CASCADE', - ...( + 'table' => [ + 'id' => $this->bigPrimaryKey()->unsigned(), + 'identifier' => $this->string()->notNull()->unique(), + 'redirect_uri' => $this->string(), + 'expiry_date_time' => $this->dateTime()->notNull(), + 'client_id' => $this->integer()->notNull(), + 'user_id' => ($userTable ? $userPkSchema->dbType : Schema::TYPE_STRING) . ' NOT NULL', + 'enabled' => $this->boolean()->notNull()->defaultValue(true), // ToDo: do we need this ??? + 'created_at' => $this->integer()->notNull(), + 'updated_at' => $this->integer()->notNull(), + ], + 'foreignKeys' => [ + [ + 'name' => 'client_id', + 'columns' => ['client_id'], + 'refTable' => $clientTable, + 'refColumns' => ['id'], + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + ...( $userTable - ? ['FOREIGN KEY (user_id) REFERENCES ' . $userTable . ' (' . $userPkColumn . ') ON DELETE CASCADE ON UPDATE CASCADE'] - : ['KEY (user_id)'] - ), - 'KEY (enabled)', + ? [ + [ + 'name' => 'user_id', + 'columns' => ['user_id'], + 'refTable' => $userTable, + 'refColumns' => $userPkColumn, + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + ] + : [] + ), + ], + 'indexes' => [ + ...( + !$userTable + ? [ + [ + 'name' => 'user_id', + 'columns' => ['user_id'], + 'unique' => false, + ], + ] + : [] + ), + [ + 'name' => 'enabled', + 'columns' => ['enabled'], + 'unique' => false, + ], + ], ], $authCodeScopeTable => [ - 'auth_code_id' => $this->bigInteger()->unsigned()->notNull(), - 'scope_id' => $this->integer()->notNull(), - 'created_at' => $this->integer()->notNull(), - - 'PRIMARY KEY (auth_code_id, scope_id)', - 'FOREIGN KEY (auth_code_id) REFERENCES ' . $authCodeTable . ' (id) ON DELETE CASCADE ON UPDATE CASCADE', - 'FOREIGN KEY (scope_id) REFERENCES ' . $scopeTable . ' (id) ON DELETE CASCADE ON UPDATE CASCADE', + 'table' => [ + 'auth_code_id' => $this->bigInteger()->unsigned()->notNull(), + 'scope_id' => $this->integer()->notNull(), + 'created_at' => $this->integer()->notNull(), + ], + 'primaryKey' => [ + 'columns' => ['auth_code_id', 'scope_id'], + ], + 'foreignKeys' => [ + [ + 'name' => 'auth_code_id', + 'columns' => ['auth_code_id'], + 'refTable' => $authCodeTable, + 'refColumns' => ['id'], + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + [ + 'name' => 'scope_id', + 'columns' => ['scope_id'], + 'refTable' => $scopeTable, + 'refColumns' => ['id'], + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + ], ], $accessTokenTable => [ - 'id' => $this->bigPrimaryKey()->unsigned(), - 'identifier' => $this->string()->notNull()->unique(), - 'client_id' => $this->integer()->notNull(), - 'user_id' => ($userTable ? $userPkSchema->dbType : Schema::TYPE_STRING) . ' DEFAULT NULL', - 'type' => $this->integer()->notNull(), - 'mac_key' => $this->string(500), - 'mac_algorithm' => Schema::TYPE_SMALLINT, - 'allowance' => Schema::TYPE_SMALLINT, - 'allowance_updated_at' => $this->integer(), - 'expiry_date_time' => $this->dateTime()->notNull(), - 'enabled' => $this->boolean()->notNull()->defaultValue(1), - 'created_at' => $this->integer()->notNull(), - 'updated_at' => $this->integer()->notNull(), - - 'FOREIGN KEY (client_id) REFERENCES ' . $clientTable . ' (id) ON DELETE CASCADE ON UPDATE CASCADE', - ...( + 'table' => [ + 'id' => $this->bigPrimaryKey()->unsigned(), + 'identifier' => $this->string()->notNull()->unique(), + 'client_id' => $this->integer()->notNull(), + 'user_id' => ($userTable ? $userPkSchema->dbType : Schema::TYPE_STRING) . ' DEFAULT NULL', + 'type' => $this->integer()->notNull(), + 'mac_key' => $this->string(500), + 'mac_algorithm' => Schema::TYPE_SMALLINT, + 'allowance' => Schema::TYPE_SMALLINT, + 'allowance_updated_at' => $this->integer(), + 'expiry_date_time' => $this->dateTime()->notNull(), + 'enabled' => $this->boolean()->notNull()->defaultValue(true), + 'created_at' => $this->integer()->notNull(), + 'updated_at' => $this->integer()->notNull(), + ], + 'foreignKeys' => [ + [ + 'name' => 'client_id', + 'columns' => ['client_id'], + 'refTable' => $clientTable, + 'refColumns' => ['id'], + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + ...( $userTable - ? ['FOREIGN KEY (user_id) REFERENCES ' . $userTable . ' (' . $userPkColumn . ') ON DELETE CASCADE ON UPDATE CASCADE'] - : ['KEY (user_id)'] - ), - 'KEY (type)', - 'KEY (mac_algorithm)', - 'KEY (enabled)', + ? [ + [ + 'name' => 'user_id', + 'columns' => ['user_id'], + 'refTable' => $userTable, + 'refColumns' => $userPkColumn, + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + ] + : [] + ), + ], + 'indexes' => [ + ...( + !$userTable + ? [ + [ + 'name' => 'user_id', + 'columns' => ['user_id'], + 'unique' => false, + ], + ] + : [] + ), + [ + 'name' => 'type', + 'columns' => ['type'], + 'unique' => false, + ], + [ + 'name' => 'mac_algorithm', + 'columns' => ['mac_algorithm'], + 'unique' => false, + ], + [ + 'name' => 'enabled', + 'columns' => ['enabled'], + 'unique' => false, + ], + ], ], $accessTokenScopeTable => [ - 'access_token_id' => $this->bigInteger()->unsigned()->notNull(), - 'scope_id' => $this->integer()->notNull(), - 'created_at' => $this->integer()->notNull(), - - 'PRIMARY KEY (access_token_id, scope_id)', - 'FOREIGN KEY (access_token_id) REFERENCES ' . $accessTokenTable . ' (id) ON DELETE CASCADE ON UPDATE CASCADE', - 'FOREIGN KEY (scope_id) REFERENCES ' . $scopeTable . ' (id) ON DELETE CASCADE ON UPDATE CASCADE', + 'table' => [ + 'access_token_id' => $this->bigInteger()->unsigned()->notNull(), + 'scope_id' => $this->integer()->notNull(), + 'created_at' => $this->integer()->notNull(), + ], + 'primaryKey' => [ + 'columns' => ['access_token_id', 'scope_id'], + ], + 'foreignKeys' => [ + [ + 'name' => 'access_token_id', + 'columns' => ['access_token_id'], + 'refTable' => $accessTokenTable, + 'refColumns' => ['id'], + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + [ + 'name' => 'scope_id', + 'columns' => ['scope_id'], + 'refTable' => $scopeTable, + 'refColumns' => ['id'], + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + ], ], $refreshTokenTable => [ - 'id' => $this->bigPrimaryKey()->unsigned(), - 'access_token_id' => $this->bigInteger()->unsigned(), - 'identifier' => $this->string()->notNull()->unique(), - 'expiry_date_time' => $this->dateTime()->notNull(), - 'enabled' => $this->boolean()->notNull()->defaultValue(1), - 'created_at' => $this->integer()->notNull(), - 'updated_at' => $this->integer()->notNull(), - - 'FOREIGN KEY (access_token_id) REFERENCES ' . $accessTokenTable . ' (id) ON DELETE CASCADE ON UPDATE CASCADE', - 'KEY (enabled)', + 'table' => [ + 'id' => $this->bigPrimaryKey()->unsigned(), + 'access_token_id' => $this->bigInteger()->unsigned(), + 'identifier' => $this->string()->notNull()->unique(), + 'expiry_date_time' => $this->dateTime()->notNull(), + 'enabled' => $this->boolean()->notNull()->defaultValue(true), + 'created_at' => $this->integer()->notNull(), + 'updated_at' => $this->integer()->notNull(), + ], + 'foreignKeys' => [ + [ + 'name' => 'access_token_id', + 'columns' => ['access_token_id'], + 'refTable' => $accessTokenTable, + 'refColumns' => ['id'], + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + ], + 'indexes' => [ + [ + 'name' => 'enabled', + 'columns' => ['enabled'], + 'unique' => false, + ], + ], ], $userClientTable => [ - 'user_id' => ($userTable ? $userPkSchema->dbType : Schema::TYPE_STRING) . ' NOT NULL', - 'client_id' => $this->integer()->notNull(), - 'enabled' => $this->boolean()->notNull()->defaultValue(1), - 'created_at' => $this->integer()->notNull(), - 'updated_at' => $this->integer()->notNull(), - - 'PRIMARY KEY (user_id, client_id)', - ...( + 'table' => [ + 'user_id' => ($userTable ? $userPkSchema->dbType : Schema::TYPE_STRING) . ' NOT NULL', + 'client_id' => $this->integer()->notNull(), + 'enabled' => $this->boolean()->notNull()->defaultValue(true), + 'created_at' => $this->integer()->notNull(), + 'updated_at' => $this->integer()->notNull(), + ], + 'primaryKey' => [ + 'columns' => ['user_id', 'client_id'], + ], + 'foreignKeys' => [ + [ + 'name' => 'client_id', + 'columns' => ['client_id'], + 'refTable' => $clientTable, + 'refColumns' => ['id'], + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + ...( $userTable - ? ['FOREIGN KEY (user_id) REFERENCES ' . $userTable . ' (' . $userPkColumn . ') ON DELETE CASCADE ON UPDATE CASCADE'] - : ['KEY (user_id)'] - ), - 'FOREIGN KEY (client_id) REFERENCES ' . $clientTable . ' (id) ON DELETE CASCADE ON UPDATE CASCADE', - 'KEY (enabled)', + ? [ + [ + 'name' => 'user_id', + 'columns' => ['user_id'], + 'refTable' => $userTable, + 'refColumns' => $userPkColumn, + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + ] + : [] + ), + ], + 'indexes' => [ + ...( + !$userTable + ? [ + [ + 'name' => 'user_id', + 'columns' => ['user_id'], + 'unique' => false, + ], + ] + : [] + ), + [ + 'name' => 'enabled', + 'columns' => ['enabled'], + 'unique' => false, + ], + ], ], $userClientScopeTable => [ - 'user_id' => ($userTable ? $userPkSchema->dbType : Schema::TYPE_STRING) . ' NOT NULL', - 'client_id' => $this->integer()->notNull(), - 'scope_id' => $this->integer()->notNull(), - 'enabled' => $this->boolean()->notNull()->defaultValue(1), - 'created_at' => $this->integer()->notNull(), - 'updated_at' => $this->integer()->notNull(), - - 'PRIMARY KEY (user_id, client_id, scope_id)', - 'FOREIGN KEY (user_id, client_id) REFERENCES ' . $userClientTable . ' (user_id, client_id) ON DELETE CASCADE ON UPDATE CASCADE', - 'FOREIGN KEY (scope_id) REFERENCES ' . $scopeTable . ' (id) ON DELETE CASCADE ON UPDATE CASCADE', # Note: Not connected to client_scope table since scopes can also be applied by default to all clients - 'KEY (enabled)', + 'table' => [ + 'user_id' => ($userTable ? $userPkSchema->dbType : Schema::TYPE_STRING) . ' NOT NULL', + 'client_id' => $this->integer()->notNull(), + 'scope_id' => $this->integer()->notNull(), + 'enabled' => $this->boolean()->notNull()->defaultValue(true), + 'created_at' => $this->integer()->notNull(), + 'updated_at' => $this->integer()->notNull(), + ], + 'primaryKey' => [ + 'columns' => ['user_id', 'client_id', 'scope_id'], + ], + 'foreignKeys' => [ + [ + 'name' => 'user_client_id', + 'columns' => ['user_id', 'client_id'], + 'refTable' => $userClientTable, + 'refColumns' => ['user_id', 'client_id'], + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + [ # Note: Not connected to client_scope table since scopes can also be applied by default to all clients + 'name' => 'scope_id', + 'columns' => ['scope_id'], + 'refTable' => $scopeTable, + 'refColumns' => ['id'], + 'delete' => static::CASCADE, + 'update' => static::CASCADE, + ], + ], + 'indexes' => [ + [ + 'name' => 'enabled', + 'columns' => ['enabled'], + 'unique' => false, + ], + ], ], ]; diff --git a/src/migrations/base/Oauth2BaseMigration.php b/src/migrations/base/Oauth2BaseMigration.php index 7f8da65..815085f 100644 --- a/src/migrations/base/Oauth2BaseMigration.php +++ b/src/migrations/base/Oauth2BaseMigration.php @@ -10,6 +10,9 @@ abstract class Oauth2BaseMigration extends Migration { + public const RESTRICT = 'RESTRICT'; + public const CASCADE = 'CASCADE'; + /** * Determines if the migration should be generated for the current module configuration. * @param Oauth2Module $module diff --git a/src/models/Oauth2Client.php b/src/models/Oauth2Client.php index 865059f..7f5f622 100644 --- a/src/models/Oauth2Client.php +++ b/src/models/Oauth2Client.php @@ -563,6 +563,7 @@ public function getAllowedScopes($requestedScopeIdentifiers = []) ->joinWith('clientScopes', true) ->enabled() ->andWhere(['OR', ...$possibleScopesConditions]) + ->orderBy('id') ->all(); } } diff --git a/src/models/base/Oauth2AccessToken.php b/src/models/base/Oauth2AccessToken.php index 9b46e9d..2bd3a37 100644 --- a/src/models/base/Oauth2AccessToken.php +++ b/src/models/base/Oauth2AccessToken.php @@ -1,5 +1,4 @@ null], + [['client_id', 'user_id', 'type', 'mac_algorithm', 'allowance', 'allowance_updated_at', 'created_at', 'updated_at'], 'integer'], [['expiry_date_time'], 'safe'], + [['enabled'], 'boolean'], [['identifier'], 'string', 'max' => 255], [['mac_key'], 'string', 'max' => 500], [['identifier'], 'unique'] @@ -70,28 +75,28 @@ public function attributeLabels() 'updated_at' => Yii::t('oauth2', 'Updated At'), ]; } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AccessTokenScopeQueryInterface */ public function getAccessTokenScopes() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2AccessTokenScope::className(), ['access_token_id' => 'id'])->inverseOf('accessToken'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ClientQueryInterface */ public function getClient() { return $this->hasOne(\rhertogh\Yii2Oauth2Server\models\Oauth2Client::className(), ['id' => 'client_id'])->inverseOf('accessTokens'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2RefreshTokenQueryInterface */ public function getRefreshTokens() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2RefreshToken::className(), ['access_token_id' => 'id'])->inverseOf('accessToken'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ScopeQueryInterface */ public function getScopes() @@ -100,7 +105,7 @@ public function getScopes() } - + /** * @inheritdoc * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AccessTokenQueryInterface the active query used by this AR class. @@ -109,4 +114,6 @@ public static function find() { return Yii::createObject(\rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AccessTokenQueryInterface::class, [get_called_class()]); } + + } diff --git a/src/models/base/Oauth2AccessTokenScope.php b/src/models/base/Oauth2AccessTokenScope.php index 05e5dae..4b6494d 100644 --- a/src/models/base/Oauth2AccessTokenScope.php +++ b/src/models/base/Oauth2AccessTokenScope.php @@ -1,5 +1,4 @@ null], [['access_token_id', 'scope_id', 'created_at'], 'integer'], [['access_token_id', 'scope_id'], 'unique', 'targetAttribute' => ['access_token_id', 'scope_id']] ]; @@ -45,14 +49,14 @@ public function attributeLabels() 'created_at' => Yii::t('oauth2', 'Created At'), ]; } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AccessTokenQueryInterface */ public function getAccessToken() { return $this->hasOne(\rhertogh\Yii2Oauth2Server\models\Oauth2AccessToken::className(), ['id' => 'access_token_id'])->inverseOf('accessTokenScopes'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ScopeQueryInterface */ public function getScope() @@ -61,7 +65,7 @@ public function getScope() } - + /** * @inheritdoc * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AccessTokenScopeQueryInterface the active query used by this AR class. @@ -70,4 +74,6 @@ public static function find() { return Yii::createObject(\rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AccessTokenScopeQueryInterface::class, [get_called_class()]); } + + } diff --git a/src/models/base/Oauth2AuthCode.php b/src/models/base/Oauth2AuthCode.php index f204ac4..ea99840 100644 --- a/src/models/base/Oauth2AuthCode.php +++ b/src/models/base/Oauth2AuthCode.php @@ -1,5 +1,4 @@ null], + [['client_id', 'user_id', 'created_at', 'updated_at'], 'integer'], + [['enabled'], 'boolean'], [['identifier', 'redirect_uri'], 'string', 'max' => 255], [['identifier'], 'unique'] ]; @@ -60,21 +65,21 @@ public function attributeLabels() 'updated_at' => Yii::t('oauth2', 'Updated At'), ]; } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AuthCodeScopeQueryInterface */ public function getAuthCodeScopes() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2AuthCodeScope::className(), ['auth_code_id' => 'id'])->inverseOf('authCode'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ClientQueryInterface */ public function getClient() { return $this->hasOne(\rhertogh\Yii2Oauth2Server\models\Oauth2Client::className(), ['id' => 'client_id'])->inverseOf('authCodes'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ScopeQueryInterface */ public function getScopes() @@ -83,7 +88,7 @@ public function getScopes() } - + /** * @inheritdoc * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AuthCodeQueryInterface the active query used by this AR class. @@ -92,4 +97,6 @@ public static function find() { return Yii::createObject(\rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AuthCodeQueryInterface::class, [get_called_class()]); } + + } diff --git a/src/models/base/Oauth2AuthCodeScope.php b/src/models/base/Oauth2AuthCodeScope.php index b1dded8..8d6896f 100644 --- a/src/models/base/Oauth2AuthCodeScope.php +++ b/src/models/base/Oauth2AuthCodeScope.php @@ -1,5 +1,4 @@ null], [['auth_code_id', 'scope_id', 'created_at'], 'integer'], [['auth_code_id', 'scope_id'], 'unique', 'targetAttribute' => ['auth_code_id', 'scope_id']] ]; @@ -45,14 +49,14 @@ public function attributeLabels() 'created_at' => Yii::t('oauth2', 'Created At'), ]; } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AuthCodeQueryInterface */ public function getAuthCode() { return $this->hasOne(\rhertogh\Yii2Oauth2Server\models\Oauth2AuthCode::className(), ['id' => 'auth_code_id'])->inverseOf('authCodeScopes'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ScopeQueryInterface */ public function getScope() @@ -61,7 +65,7 @@ public function getScope() } - + /** * @inheritdoc * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AuthCodeScopeQueryInterface the active query used by this AR class. @@ -70,4 +74,6 @@ public static function find() { return Yii::createObject(\rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AuthCodeScopeQueryInterface::class, [get_called_class()]); } + + } diff --git a/src/models/base/Oauth2Client.php b/src/models/base/Oauth2Client.php index 31023ba..a70ad22 100644 --- a/src/models/base/Oauth2Client.php +++ b/src/models/base/Oauth2Client.php @@ -1,5 +1,4 @@ null], + [['type', 'token_types', 'grant_types', 'scope_access', 'user_account_selection', 'client_credentials_grant_user_id', 'created_at', 'updated_at'], 'integer'], [['secret', 'old_secret', 'contacts'], 'string'], [['old_secret_valid_until', 'redirect_uris'], 'safe'], + [['allow_auth_code_without_pkce', 'skip_authorization_if_scope_is_allowed', 'oidc_allow_offline_access_without_consent', 'enabled'], 'boolean'], [['identifier', 'name', 'logo_uri', 'tos_uri', 'oidc_userinfo_encrypted_response_alg'], 'string', 'max' => 255], [['identifier'], 'unique'] ]; @@ -91,35 +96,35 @@ public function attributeLabels() 'updated_at' => Yii::t('oauth2', 'Updated At'), ]; } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AccessTokenQueryInterface */ public function getAccessTokens() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2AccessToken::className(), ['client_id' => 'id'])->inverseOf('client'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AuthCodeQueryInterface */ public function getAuthCodes() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2AuthCode::className(), ['client_id' => 'id'])->inverseOf('client'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ClientScopeQueryInterface */ public function getClientScopes() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2ClientScope::className(), ['client_id' => 'id'])->inverseOf('client'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ScopeQueryInterface */ public function getScopes() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2Scope::className(), ['id' => 'scope_id'])->via('clientScopes'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2UserClientQueryInterface */ public function getUserClients() @@ -128,7 +133,7 @@ public function getUserClients() } - + /** * @inheritdoc * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ClientQueryInterface the active query used by this AR class. @@ -137,4 +142,6 @@ public static function find() { return Yii::createObject(\rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ClientQueryInterface::class, [get_called_class()]); } + + } diff --git a/src/models/base/Oauth2ClientScope.php b/src/models/base/Oauth2ClientScope.php index c01fee3..5cede08 100644 --- a/src/models/base/Oauth2ClientScope.php +++ b/src/models/base/Oauth2ClientScope.php @@ -1,5 +1,4 @@ null], + [['client_id', 'scope_id', 'applied_by_default', 'created_at', 'updated_at'], 'integer'], + [['required_on_authorization', 'enabled'], 'boolean'], [['client_id', 'scope_id'], 'unique', 'targetAttribute' => ['client_id', 'scope_id']] ]; } @@ -53,14 +58,14 @@ public function attributeLabels() 'updated_at' => Yii::t('oauth2', 'Updated At'), ]; } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ClientQueryInterface */ public function getClient() { return $this->hasOne(\rhertogh\Yii2Oauth2Server\models\Oauth2Client::className(), ['id' => 'client_id'])->inverseOf('clientScopes'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ScopeQueryInterface */ public function getScope() @@ -69,7 +74,7 @@ public function getScope() } - + /** * @inheritdoc * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ClientScopeQueryInterface the active query used by this AR class. @@ -78,4 +83,6 @@ public static function find() { return Yii::createObject(\rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ClientScopeQueryInterface::class, [get_called_class()]); } + + } diff --git a/src/models/base/Oauth2RefreshToken.php b/src/models/base/Oauth2RefreshToken.php index e7f146d..06388a0 100644 --- a/src/models/base/Oauth2RefreshToken.php +++ b/src/models/base/Oauth2RefreshToken.php @@ -1,5 +1,4 @@ null], + [['access_token_id', 'created_at', 'updated_at'], 'integer'], [['identifier', 'expiry_date_time', 'created_at', 'updated_at'], 'required'], [['expiry_date_time'], 'safe'], + [['enabled'], 'boolean'], [['identifier'], 'string', 'max' => 255], [['identifier'], 'unique'] ]; @@ -54,7 +59,7 @@ public function attributeLabels() 'updated_at' => Yii::t('oauth2', 'Updated At'), ]; } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AccessTokenQueryInterface */ public function getAccessToken() @@ -63,7 +68,7 @@ public function getAccessToken() } - + /** * @inheritdoc * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2RefreshTokenQueryInterface the active query used by this AR class. @@ -72,4 +77,6 @@ public static function find() { return Yii::createObject(\rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2RefreshTokenQueryInterface::class, [get_called_class()]); } + + } diff --git a/src/models/base/Oauth2Scope.php b/src/models/base/Oauth2Scope.php index 2acf972..122bb2f 100644 --- a/src/models/base/Oauth2Scope.php +++ b/src/models/base/Oauth2Scope.php @@ -1,5 +1,4 @@ 255], + [['applied_by_default', 'created_at', 'updated_at'], 'default', 'value' => null], + [['applied_by_default', 'created_at', 'updated_at'], 'integer'], + [['required_on_authorization', 'enabled'], 'boolean'], + [['identifier'], 'string', 'max' => 255], [['identifier'], 'unique'] ]; } @@ -65,56 +70,56 @@ public function attributeLabels() 'updated_at' => Yii::t('oauth2', 'Updated At'), ]; } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AccessTokenScopeQueryInterface */ public function getAccessTokenScopes() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2AccessTokenScope::className(), ['scope_id' => 'id'])->inverseOf('scope'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AccessTokenQueryInterface */ public function getAccessTokens() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2AccessToken::className(), ['id' => 'access_token_id'])->via('accessTokenScopes'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AuthCodeScopeQueryInterface */ public function getAuthCodeScopes() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2AuthCodeScope::className(), ['scope_id' => 'id'])->inverseOf('scope'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2AuthCodeQueryInterface */ public function getAuthCodes() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2AuthCode::className(), ['id' => 'auth_code_id'])->via('authCodeScopes'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ClientScopeQueryInterface */ public function getClientScopes() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2ClientScope::className(), ['scope_id' => 'id'])->inverseOf('scope'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ClientQueryInterface */ public function getClients() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2Client::className(), ['id' => 'client_id'])->via('clientScopes'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2UserClientScopeQueryInterface */ public function getUserClientScopes() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2UserClientScope::className(), ['scope_id' => 'id'])->inverseOf('scope'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2UserClientQueryInterface */ public function getUserClients() @@ -123,7 +128,7 @@ public function getUserClients() } - + /** * @inheritdoc * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ScopeQueryInterface the active query used by this AR class. @@ -132,4 +137,6 @@ public static function find() { return Yii::createObject(\rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ScopeQueryInterface::class, [get_called_class()]); } + + } diff --git a/src/models/base/Oauth2UserClient.php b/src/models/base/Oauth2UserClient.php index f06a480..ccf0089 100644 --- a/src/models/base/Oauth2UserClient.php +++ b/src/models/base/Oauth2UserClient.php @@ -1,5 +1,4 @@ null], + [['user_id', 'client_id', 'created_at', 'updated_at'], 'integer'], + [['enabled'], 'boolean'], [['user_id', 'client_id'], 'unique', 'targetAttribute' => ['user_id', 'client_id']] ]; } @@ -50,21 +55,21 @@ public function attributeLabels() 'updated_at' => Yii::t('oauth2', 'Updated At'), ]; } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ClientQueryInterface */ public function getClient() { return $this->hasOne(\rhertogh\Yii2Oauth2Server\models\Oauth2Client::className(), ['id' => 'client_id'])->inverseOf('userClients'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ScopeQueryInterface */ public function getScopes() { return $this->hasMany(\rhertogh\Yii2Oauth2Server\models\Oauth2Scope::className(), ['id' => 'scope_id'])->via('userClientScopes'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2UserClientScopeQueryInterface */ public function getUserClientScopes() @@ -73,7 +78,7 @@ public function getUserClientScopes() } - + /** * @inheritdoc * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2UserClientQueryInterface the active query used by this AR class. @@ -82,4 +87,6 @@ public static function find() { return Yii::createObject(\rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2UserClientQueryInterface::class, [get_called_class()]); } + + } diff --git a/src/models/base/Oauth2UserClientScope.php b/src/models/base/Oauth2UserClientScope.php index 4798537..304ebe2 100644 --- a/src/models/base/Oauth2UserClientScope.php +++ b/src/models/base/Oauth2UserClientScope.php @@ -1,5 +1,4 @@ null], + [['user_id', 'client_id', 'scope_id', 'created_at', 'updated_at'], 'integer'], + [['enabled'], 'boolean'], [['user_id', 'client_id', 'scope_id'], 'unique', 'targetAttribute' => ['user_id', 'client_id', 'scope_id']] ]; } @@ -51,14 +56,14 @@ public function attributeLabels() 'updated_at' => Yii::t('oauth2', 'Updated At'), ]; } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2ScopeQueryInterface */ public function getScope() { return $this->hasOne(\rhertogh\Yii2Oauth2Server\models\Oauth2Scope::className(), ['id' => 'scope_id'])->inverseOf('userClientScopes'); } - + /** * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2UserClientQueryInterface */ public function getUserClient() @@ -67,7 +72,7 @@ public function getUserClient() } - + /** * @inheritdoc * @return \rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2UserClientScopeQueryInterface the active query used by this AR class. @@ -76,4 +81,6 @@ public static function find() { return Yii::createObject(\rhertogh\Yii2Oauth2Server\interfaces\models\queries\Oauth2UserClientScopeQueryInterface::class, [get_called_class()]); } + + } diff --git a/src/models/behaviors/BooleanBehavior.php b/src/models/behaviors/BooleanBehavior.php index 7528b0d..793deca 100644 --- a/src/models/behaviors/BooleanBehavior.php +++ b/src/models/behaviors/BooleanBehavior.php @@ -11,12 +11,11 @@ class BooleanBehavior extends Behavior /** * Nummeric database schema types. */ - public const NUMERIC_SCHEMA_TYPES = [ + public const INTEGER_SCHEMA_TYPES = [ Schema::TYPE_INTEGER, Schema::TYPE_BIGINT, Schema::TYPE_SMALLINT, Schema::TYPE_TINYINT, - Schema::TYPE_FLOAT, ]; /** @@ -25,9 +24,11 @@ class BooleanBehavior extends Behavior public function events() { return [ - ActiveRecord::EVENT_BEFORE_VALIDATE => 'boolToInt', ActiveRecord::EVENT_BEFORE_INSERT => 'boolToInt', ActiveRecord::EVENT_BEFORE_UPDATE => 'boolToInt', + ActiveRecord::EVENT_AFTER_INSERT => 'intToBool', + ActiveRecord::EVENT_AFTER_UPDATE => 'intToBool', + ActiveRecord::EVENT_AFTER_FIND => 'intToBool', ]; } @@ -42,9 +43,29 @@ public function boolToInt() $activeRecord = $this->owner; foreach ($activeRecord->getTableSchema()->columns as $column) { - if (is_bool($activeRecord->{$column->name}) && in_array($column->type, static::NUMERIC_SCHEMA_TYPES)) { + if (is_bool($activeRecord->{$column->name}) && in_array($column->type, static::INTEGER_SCHEMA_TYPES)) { $activeRecord->{$column->name} = $activeRecord->{$column->name} ? 1 : 0; } } } + + /** + * Convert integer values to booleans after loading them from the database. + * @throws \yii\base\InvalidConfigException + * @since 1.0.0 + */ + public function intToBool() + { + /** @var ActiveRecord $activeRecord */ + $activeRecord = $this->owner; + + foreach ($activeRecord->getTableSchema()->columns as $column) { + if ( + is_int($activeRecord->{$column->name}) + && $column->size === 1 + ) { + $activeRecord->{$column->name} = (bool)$activeRecord->{$column->name}; + } + } + } } diff --git a/src/models/behaviors/DateTimeBehavior.php b/src/models/behaviors/DateTimeBehavior.php index 2e43963..fe55a60 100644 --- a/src/models/behaviors/DateTimeBehavior.php +++ b/src/models/behaviors/DateTimeBehavior.php @@ -2,7 +2,6 @@ namespace rhertogh\Yii2Oauth2Server\models\behaviors; -use DateTimeImmutable; use yii\base\Behavior; use yii\db\ActiveRecord; use yii\db\BaseActiveRecord; @@ -46,8 +45,8 @@ public function beforeSave() foreach ($activeRecord->getTableSchema()->columns as $column) { $value = $this->owner[$column->name]; if ( - $column->type === Schema::TYPE_DATETIME - && ($value instanceof \DateTime || $value instanceof DateTimeImmutable) + in_array($column->type, [Schema::TYPE_DATE, Schema::TYPE_DATETIME, Schema::TYPE_TIMESTAMP]) + && ($value instanceof \DateTime || $value instanceof \DateTimeImmutable) ) { $this->owner[$column->name] = $value->format($this->dateTimeFormat); } @@ -66,8 +65,11 @@ public function afterFind() foreach ($activeRecord->getTableSchema()->columns as $column) { $value = $this->owner[$column->name]; - if ($column->type === Schema::TYPE_DATETIME && !empty($value)) { - $dateTime = DateTimeImmutable::createFromFormat($this->dateTimeFormat, $value); + if ( + in_array($column->type, [Schema::TYPE_DATETIME, Schema::TYPE_DATETIME, Schema::TYPE_TIMESTAMP]) + && !empty($value) + ) { + $dateTime = \DateTimeImmutable::createFromFormat($this->dateTimeFormat, $value); $this->owner[$column->name] = $dateTime; $this->owner->setOldAttribute($column->name, $dateTime); } diff --git a/tests/.env.example b/tests/.env.example index 8ce1dc5..51ae185 100644 --- a/tests/.env.example +++ b/tests/.env.example @@ -1,7 +1,7 @@ # WARNING! # These variables are provided for sample use only. NEVER use them for other purposes! -# Sample App +# App MYSQL_HOST="mysql" YII2_OAUTH2_SERVER_PRIVATE_KEY_PASSPHRASE="secret" YII2_OAUTH2_SERVER_CODES_ENCRYPTION_KEY="def00000761b6fce5b2c1721c37602e82effb785154c3bb0db93bfb3f413012bd85d46461e28f156a3a5afab910a64d5b2665276d45f24b1085d90e12ab3d38ee47b4337" @@ -11,13 +11,18 @@ YII2_OAUTH2_SERVER_STORAGE_ENCRYPTION_KEYS="{ }" DOCKER_HOST_HTTP_PORT="88" # Optional, the port on the docker host machine where the Yii2Oauth2Server container http port will be mapped to (it's recommended to use a different port than the default sample app). -# Sample App Mysql +# MySql MYSQL_ROOT_PASSWORD="password" MYSQL_DB_NAME="Yii2Oauth2ServerTest" MYSQL_USER_NAME="Yii2Oauth2ServerTest" MYSQL_USER_PASSWORD="password" DOCKER_HOST_MYSQL_PORT="3307" # Optional, the port on the docker host machine where the MySql container port will be mapped to (it's recommended to use a different port than the default sample app). +# PostgreSQL +POSTGRES_USER="root" +POSTGRES_PASSWORD="password" +POSTGRES_DB="Yii2Oauth2ServerTest" + # Xdebug XDEBUG_CONFIG="log_level=7" XDEBUG_MODE="develop,debug,coverage" # Workaround for https://youtrack.jetbrains.com/issue/WI-57304 diff --git a/tests/_config/site.php b/tests/_config/site.php index 292cb70..6366ace 100644 --- a/tests/_config/site.php +++ b/tests/_config/site.php @@ -34,19 +34,6 @@ 'response' => [ 'class' => NoHeadersResponse::class, ], - 'db' => [ - 'class' => yii\db\Connection::class, - 'dsn' => getenv('MYSQL_HOST') && getenv('MYSQL_DB_NAME') - ? 'mysql:host=' . getenv('MYSQL_HOST') . (getenv('MYSQL_PORT') ? ':' . getenv('MYSQL_PORT') : '') - . ';dbname=' . getenv('MYSQL_DB_NAME') - : null, - 'username' => getenv('MYSQL_USER_NAME'), - 'password' => getenv('MYSQL_USER_PASSWORD'), - 'charset' => 'utf8mb4', - 'enableSchemaCache' => false, - 'enableLogging' => YII_DEBUG, - 'enableProfiling' => YII_DEBUG, - ], 'errorHandler' => [ 'silentExitOnException' => true, ], diff --git a/tests/_data/config.php b/tests/_data/config.php index 668951f..197e801 100644 --- a/tests/_data/config.php +++ b/tests/_data/config.php @@ -4,9 +4,9 @@ 'databases' => [ 'cubrid' => [ 'connection' => [ - 'dsn' => 'cubrid:dbname=demodb;host=cubrid;port=33000', - 'username' => 'dba', - 'password' => '', + 'dsn' => 'cubrid:dbname=demodb;host=cubrid;port=33000', + 'username' => 'dba', + 'password' => '', ], 'fixture' => __DIR__ . '/cubrid.sql', ], @@ -34,6 +34,30 @@ ...(is_file(__DIR__ . '/mysql_post.local.sql') ? [__DIR__ . '/mysql_post.local.sql'] : []), ], ], + 'postgres' => [ + 'connection' => [ + 'dsn' => + 'pgsql:host=' . getenv('POSTGRES_HOST') + . (getenv('POSTGRES_PORT') ? ':' . getenv('POSTGRES_PORT') : '') + . ';dbname=' . getenv('POSTGRES_DB'), + 'username' => getenv('POSTGRES_USER'), + 'password' => getenv('POSTGRES_PASSWORD'), + 'charset' => 'utf8', + ], + 'preMigrationsFixtures' => [ + __DIR__ . '/postgres_pre.sql', + ...(is_file(__DIR__ . '/postgres_pre.local.sql') ? [__DIR__ . '/postgres_pre.local.sql'] : []), + ], + 'migrations' => [ + 'migrationNamespaces' => [ + 'rhertogh\\Yii2Oauth2Server\\migrations', + ], + ], + 'postMigrationsFixtures' => [ + __DIR__ . '/postgres_post.sql', + ...(is_file(__DIR__ . '/postgres_post.local.sql') ? [__DIR__ . '/postgres_post.local.sql'] : []), + ], + ], 'sqlite' => [ 'connection' => [ 'dsn' => 'sqlite::memory:', diff --git a/tests/_data/mysql_post.sql b/tests/_data/mysql_post.sql index 4b93c08..b54b128 100644 --- a/tests/_data/mysql_post.sql +++ b/tests/_data/mysql_post.sql @@ -1,5 +1,5 @@ /** - * This is the database schema for testing MySQL support of Yii DAO and Active Record. + * This is the database schema for testing MySQL support. * The database setup in config.php is required to perform then relevant tests: */ @@ -18,19 +18,19 @@ SELECT @oidcOfflineAccessScopeId := `id` FROM `oauth2_scope` WHERE `identifier` INSERT INTO `oauth2_client` ( - id, - identifier, - type, - secret, - name, - redirect_uris, - token_types, - grant_types, - skip_authorization_if_scope_is_allowed, - client_credentials_grant_user_id, - enabled, - created_at, - updated_at + `id`, + `identifier`, + `type`, + `secret`, + `name`, + `redirect_uris`, + `token_types`, + `grant_types`, + `skip_authorization_if_scope_is_allowed`, + `client_credentials_grant_user_id`, + `enabled`, + `created_at`, + `updated_at` ) VALUES ( diff --git a/tests/_data/mysql_pre.sql b/tests/_data/mysql_pre.sql index 3100d33..b0a76d8 100644 --- a/tests/_data/mysql_pre.sql +++ b/tests/_data/mysql_pre.sql @@ -1,5 +1,5 @@ /** - * This is the database schema for testing MySQL support of Yii DAO and Active Record. + * This is the database schema for testing MySQL support. * The database setup in config.php is required to perform then relevant tests: */ diff --git a/tests/_data/postgres_post.sql b/tests/_data/postgres_post.sql new file mode 100644 index 0000000..1c2ecf3 --- /dev/null +++ b/tests/_data/postgres_post.sql @@ -0,0 +1,928 @@ +/** + * This is the database schema for testing PostgreSQL support. + * The database setup in config.php is required to perform then relevant tests: + */ +ALTER SEQUENCE oauth2_access_token_id_seq RESTART WITH 1000; -- oauth2_access_token fixture range: 1001000 +ALTER SEQUENCE oauth2_auth_code_id_seq RESTART WITH 2000; -- oauth2_auth_code fixture range: 1002000 +ALTER SEQUENCE oauth2_client_id_seq RESTART WITH 3000; -- oauth2_client fixture range: 1003000 +ALTER SEQUENCE oauth2_refresh_token_id_seq RESTART WITH 4000; -- oauth2_refresh_token fixture range: 1004000 +ALTER SEQUENCE oauth2_scope_id_seq RESTART WITH 5000; -- oauth2_scope fixture range: 1005000 + +SET SESSION Yii2Oauth2Server.PostMigration.oidcScopeId = 1; +SET SESSION Yii2Oauth2Server.PostMigration.oidcProfileScopeId = 2; +SET SESSION Yii2Oauth2Server.PostMigration.oidcEmailScopeId = 3; +SET SESSION Yii2Oauth2Server.PostMigration.oidcAddressScopeId = 4; +SET SESSION Yii2Oauth2Server.PostMigration.oidcPhoneScopeId = 5; +SET SESSION Yii2Oauth2Server.PostMigration.oidcOfflineAccessScopeId = 6; + +INSERT INTO "oauth2_client" + ( + "id", + "identifier", + "type", + "secret", + "name", + "redirect_uris", + "token_types", + "grant_types", + "skip_authorization_if_scope_is_allowed", + "client_credentials_grant_user_id", + "enabled", + "created_at", + "updated_at" + ) + VALUES + ( + 1003000, + 'test-client-type-auth-code-valid', + 1, -- Confidential + '2021-01-01::3vUCADtKx59NPQl3/1fJXmppRbiug3iccJc1S9XY6TPvLE02/+ggB8GtIc24J5oMTj38NIPIpNt8ClNDS7ZBI4+ykNxYOuEHQfdkDiUf5WVKtLegx43gLXfq', -- "secret" + 'Valid client with Grant Type Auth Code', + '["http://localhost/redirect_uri/"]', + 1, -- Bearer + 5, -- AUTH_CODE | REFRESH_TOKEN + false, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003001, + 'test-client-type-client-credentials-valid', + 1, -- Confidential + '2021-01-01::3vUCADtKx59NPQl3/1fJXmppRbiug3iccJc1S9XY6TPvLE02/+ggB8GtIc24J5oMTj38NIPIpNt8ClNDS7ZBI4+ykNxYOuEHQfdkDiUf5WVKtLegx43gLXfq', -- "secret" + 'Valid client with Grant Type client credentials', + null, + 1, -- Bearer + 2, -- CLIENT_CREDENTIALS // refresh token SHOULD NOT be included: https://datatracker.ietf.org/doc/html/rfc6749--section-4.4.3 + false, + 123, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003002, + 'test-client-type-password-public-valid', + 2, -- Public + NULL, + 'Valid client with Grant Type password', + '["http://localhost/redirect_uri/"]', + 1, -- Bearer + 1028, -- PASSWORD | REFRESH_TOKEN + false, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003003, + 'test-client-type-implicit-valid', + 1, -- Confidential + '2021-01-01::3vUCADtKx59NPQl3/1fJXmppRbiug3iccJc1S9XY6TPvLE02/+ggB8GtIc24J5oMTj38NIPIpNt8ClNDS7ZBI4+ykNxYOuEHQfdkDiUf5WVKtLegx43gLXfq', -- "secret", + 'Valid client with Grant Type Implicit', + '["http://localhost/redirect_uri/"]', + 1, -- Bearer + 2052, -- IMPLICIT // The authorization server MUST NOT issue a refresh token: https://datatracker.ietf.org/doc/html/rfc6749--section-4.2.2 + false, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003004, + 'test-client-type-auth-code-disabled', + 1, -- Confidential + '2021-01-01::3vUCADtKx59NPQl3/1fJXmppRbiug3iccJc1S9XY6TPvLE02/+ggB8GtIc24J5oMTj38NIPIpNt8ClNDS7ZBI4+ykNxYOuEHQfdkDiUf5WVKtLegx43gLXfq', -- "secret" + 'Disabled client with Grant Type Auth Code', + '["http://localhost/redirect_uri/"]', + 1, -- Bearer + 5, -- AUTH_CODE | REFRESH_TOKEN + false, + null, + false, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003005, + 'test-client-type-auth-code-no-scopes', + 2, -- Public + null, + 'Valid public client with Grant Type Auth Code without scopes', + '["http://localhost/redirect_uri/"]', + 1, -- Bearer + 5, -- AUTH_CODE | REFRESH_TOKEN + false, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003006, + 'test-client-type-auth-code-open-id-connect', + 2, -- Public + null, + 'Valid client with Grant Type Auth Code and OpenID connect', + '["http://localhost/redirect_uri/"]', + 1, -- Bearer + 5, -- AUTH_CODE | REFRESH_TOKEN + false, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + -- Note: for testing the offline access scope is not auto applied + 1003007, + 'test-client-type-auth-code-open-id-connect-skip-authorization', + 2, -- Public + null, + 'Valid client with Grant Type Auth Code and OpenID connect skip authorization if scope allowed', + '["http://localhost/redirect_uri/"]', + 1, -- Bearer + 5, -- AUTH_CODE | REFRESH_TOKEN + true, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ); + +INSERT INTO "oauth2_scope" VALUES + ( + 1005000, + 'user.id.read', + 'Read user id', + 'See your user id.', + 1, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005001, + 'user.username.read', + 'Read username', + 'See your username.', + 0, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005002, + 'user.email_address.read', + 'Read user email address', + 'See your email address.', + 0, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005003, + 'user.enabled.read', + 'Read enabled status', + 'See if your account is enabled.', + 0, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005004, + 'user.created_at.read', + 'Read created_at timestamp', + 'See when your account was created.', + 0, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005005, + 'user.updated_at.read', + 'Read updated_at timestamp', + 'See when your account was last changed.', + 0, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005006, + 'disabled-scope', + 'disabled scope', + null, + 0, + true, + false, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005007, + 'disabled-scope-for-client', + 'disabled scope for client (enabled itself, but disabled in oauth2_client_scope)', + null, + 0, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005008, + 'defined-but-not-assigned', + 'defined, but not assigned to any client', + null, + 0, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005009, + 'applied-by-default', + 'applied by default based on client.scope_access', + null, + 1, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005010, + 'applied-by-default-for-client', + 'applied by default based for client (not applied itself, but applied in oauth2_client_scope)', + null, + 0, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005011, + 'applied-automatically-by-default', + 'applied automatically (no user confirmation) by default based on client', + null, + 2, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005012, + 'applied-automatically-by-default-for-client', + 'applied automatically (no user confirmation) by default for client (not applied itself, but applied in oauth2_client_scope)', + null, + 0, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005013, + 'applied-automatically-by-default-not-assigned', + 'applied automatically (no user confirmation) by default not assigned to client', + null, + 2, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005014, + 'applied-automatically-by-default-not-assigned-not-required', + 'applied automatically (no user confirmation) by default not assigned to client and not required', + null, + 2, + false, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005015, + 'applied-by-default-by-client-not-required-for-client', + 'required by default, but not required for client', + null, + 0, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005016, + 'pre-assigned-for-user-test', + 'previously assigned for user test', + null, + 0, + true, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005017, + 'not-required', + 'not required', + null, + 0, + false, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1005018, + 'not-required-has-been-rejected-before', + 'not required, rejected before for some test users', + null, + 0, + false, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ); +; + +INSERT INTO "oauth2_client_scope" VALUES + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005000, -- 'user.id.read' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005001, -- 'user.username.read' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005002, -- 'user.email_address.read' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005003, -- 'user.enabled.read' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005004, -- 'user.created_at.read' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005005, -- 'user.updated_at.read' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005006, -- 'disabled-scope' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005007, -- 'disabled-scope-for-client' + null, + null, + false, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005009, -- 'applied-by-default' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005010, -- 'applied-by-default-for-client' + 1, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005011, -- 'applied-automatically-by-default' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005012, -- 'applied-automatically-by-default-for-client' + 2, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005015, -- 'applied-automatically-by-client-not-required-for-client' + 1, + false, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005016, -- 'pre-assigned-for-user-test' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003000, -- 'test-client-type-auth-code-valid' + 1005017, -- 'not-required', + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + -- test-client-type-client-credentials-valid + ( + 1003001, -- 'test-client-type-client-credentials-valid' + 1005000, -- 'user.id.read' + 2, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003001, -- 'test-client-type-client-credentials-valid' + 1005001, -- 'user.username.read' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003001, -- 'test-client-type-client-credentials-valid' + 1005002, -- 'user.email_address.read' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003001, -- 'test-client-type-client-credentials-valid' + 1005003, -- 'user.enabled.read' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + -- test-client-type-password-public-valid + ( + 1003002, -- 'test-client-type-password-public-valid' + 1005000, -- 'user.id.read' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003002, -- 'test-client-type-password-public-valid' + 1005001, -- 'user.username.read' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003002, -- 'test-client-type-password-public-valid' + 1005002, -- 'user.email_address.read' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003002, -- 'test-client-type-password-public-valid' + 1005003, -- 'user.enabled.read' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003002, -- 'test-client-type-password-public-valid' + 1005012, -- 'applied-automatically-by-default-for-client' + 2, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003002, -- 'test-client-type-password-public-valid' + 1005017, -- 'not-required', + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003002, -- 'test-client-type-password-public-valid' + 1005018, -- 'not-required-has-been-rejected-before' + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + -- test-client-type-auth-code-open-id-connect + ( + 1003006, -- 'test-client-type-auth-code-open-id-connect' + current_setting('Yii2Oauth2Server.PostMigration.oidcScopeId')::int, + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003006, -- 'test-client-type-auth-code-open-id-connect' + current_setting('Yii2Oauth2Server.PostMigration.oidcProfileScopeId')::int, + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003006, -- 'test-client-type-auth-code-open-id-connect' + current_setting('Yii2Oauth2Server.PostMigration.oidcEmailScopeId')::int, + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003006, -- 'test-client-type-auth-code-open-id-connect' + current_setting('Yii2Oauth2Server.PostMigration.oidcAddressScopeId')::int, + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003006, -- 'test-client-type-auth-code-open-id-connect' + current_setting('Yii2Oauth2Server.PostMigration.oidcPhoneScopeId')::int, + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003006, -- 'test-client-type-auth-code-open-id-connect' + current_setting('Yii2Oauth2Server.PostMigration.oidcOfflineAccessScopeId')::int, + null, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + -- test-client-type-auth-code-open-id-connect-skip-authorization , + ( + 1003007, -- 'test-client-type-auth-code-open-id-connect-skip-authorization' + current_setting('Yii2Oauth2Server.PostMigration.oidcScopeId')::int, + 2, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003007, -- 'test-client-type-auth-code-open-id-connect-skip-authorization' + current_setting('Yii2Oauth2Server.PostMigration.oidcProfileScopeId')::int, + 2, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003007, -- 'test-client-type-auth-code-open-id-connect-skip-authorization' + current_setting('Yii2Oauth2Server.PostMigration.oidcEmailScopeId')::int, + 2, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003007, -- 'test-client-type-auth-code-open-id-connect-skip-authorization' + current_setting('Yii2Oauth2Server.PostMigration.oidcAddressScopeId')::int, + 2, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003007, -- 'test-client-type-auth-code-open-id-connect-skip-authorization' + current_setting('Yii2Oauth2Server.PostMigration.oidcPhoneScopeId')::int, + 2, + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1003007, -- 'test-client-type-auth-code-open-id-connect-skip-authorization' + current_setting('Yii2Oauth2Server.PostMigration.oidcOfflineAccessScopeId')::int, + 0, -- Note: for testing the offline access scope is not auto applied + null, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ); + +INSERT INTO "oauth2_access_token" + ( + id, + identifier, + client_id, + user_id, + type, + expiry_date_time, + enabled, + created_at, + updated_at + ) + VALUES + ( + 1001000, + 'test-access-token-bearer-active', + 1003000, + 123, + 1, -- Bearer + '2222-01-01', + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1001001, + 'test-access-token-bearer-expired', + 1003000, + 123, + 1, -- Bearer + '2021-01-01', + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1001002, + 'test-access-token-bearer-disabled', + 1003000, + 123, + 1, -- Bearer + '2222-01-01', + false, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ); + +INSERT INTO "oauth2_auth_code" + ( + id, + identifier, + redirect_uri, + expiry_date_time, + client_id, + user_id, + enabled, + created_at, + updated_at + ) + VALUES + ( + 1002000, + 'test-auth-code-valid', + 'http://localhost/redirect_uri/', + '2222-01-01', + 1003000, + 123, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1002001, + 'test-auth-code-expired', + 'http://localhost/redirect_uri/', + '2021-01-01', + 1003000, + 123, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1002002, + 'test-auth-code-disabled', + 'http://localhost/redirect_uri/', + '2222-01-01', + 1003000, + 123, + false, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ); + +INSERT INTO "oauth2_refresh_token" + ( + id, + access_token_id, + identifier, + expiry_date_time, + enabled, + created_at, + updated_at + ) + VALUES + ( + 1004000, + 1001000, + 'test-refresh-token-valid', + '2222-01-01', + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1004001, + 1001000, + 'test-refresh-token-expired', + '2021-01-01', + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 1004002, + 1001000, + 'test-refresh-token-disabled', + '2222-01-01', + false, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ); + +INSERT INTO oauth2_user_client VALUES + ( + 123, -- test.user + 1003001, -- 'test-client-type-client-credentials-valid' + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 124, -- test.user2 + 1003000, -- 'test-client-type-auth-code-valid' + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 124, -- test.user2 + 1003002, -- 'test-client-type-password-public-valid' + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 124, -- test.user2 + 1003007, -- 'test-client-type-auth-code-open-id-connect-skip-authorization' + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ); + +INSERT INTO oauth2_user_client_scope VALUES + ( + 123, -- test.user + 1003001, -- 'test-client-type-client-credentials-valid' + 1005001, -- 'user.username.read' + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 124, -- test.user2 + 1003000, -- 'test-client-type-auth-code-valid' + 1005016, -- 'pre-assigned-for-user-test' + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 124, -- test.user2 + 1003002, -- 'test-client-type-password-public-valid' + 1005000, -- 'user.id.read' + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 124, -- test.user2 + 1003002, -- 'test-client-type-password-public-valid' + 1005001, -- 'user.username.read' + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 124, -- test.user2 + 1003002, -- 'test-client-type-password-public-valid' + 1005002, -- 'user.email_address.read' + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ), + ( + 124, -- test.user2 + 1003002, -- 'test-client-type-password-public-valid' + 1005018, -- 'not-required-has-been-rejected-before', + false, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) + ); diff --git a/tests/_data/postgres_pre.sql b/tests/_data/postgres_pre.sql new file mode 100644 index 0000000..3a2185f --- /dev/null +++ b/tests/_data/postgres_pre.sql @@ -0,0 +1,56 @@ +/** + * This is the database schema for testing PostgreSQL support. + * The database setup in config.php is required to perform then relevant tests: + */ + +DROP TABLE IF EXISTS "migration" CASCADE; + +DROP TABLE IF EXISTS "oauth2_user_client_scope" CASCADE; +DROP TABLE IF EXISTS "oauth2_user_client" CASCADE; +DROP TABLE IF EXISTS "oauth2_access_token_scope" CASCADE; +DROP TABLE IF EXISTS "oauth2_auth_code_scope" CASCADE; +DROP TABLE IF EXISTS "oauth2_auth_code" CASCADE; +DROP TABLE IF EXISTS "oauth2_client_scope" CASCADE; +DROP TABLE IF EXISTS "oauth2_refresh_token" CASCADE; +DROP TABLE IF EXISTS "oauth2_access_token" CASCADE; +DROP TABLE IF EXISTS "oauth2_client" CASCADE; +DROP TABLE IF EXISTS "oauth2_scope" CASCADE; + +DROP TABLE IF EXISTS "user_identity_link" CASCADE; -- in case the sample migrations were run against the test database +DROP TABLE IF EXISTS "user" CASCADE; + +CREATE TABLE "user" +( + "id" SERIAL PRIMARY KEY, + "username" VARCHAR(255) NOT NULL, + "password_hash" VARCHAR(255) NOT NULL, + "email_address" VARCHAR(255) NOT NULL, + "latest_authenticated_at" INT, + "enabled" boolean DEFAULT true NOT NULL, + "created_at" INT NOT NULL, + "updated_at" INT NOT NULL +); + +ALTER SEQUENCE user_id_seq RESTART WITH 1000; + +INSERT INTO "user" VALUES +( + 123, + 'test.user', + '$2y$10$PtIeyOB1.rPPVHjgTzCO5eSNPS1vdOCNp4nk1IvA2FKYu6jslFVNK', -- "password" + 'test.user@test.test', + EXTRACT(EPOCH FROM NOW()) - 3600, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) +), +( + 124, + 'test.user2', + '$2y$10$PtIeyOB1.rPPVHjgTzCO5eSNPS1vdOCNp4nk1IvA2FKYu6jslFVNK', -- "password" + 'test.user2@test.test', + EXTRACT(EPOCH FROM NOW()) - 3600, + true, + EXTRACT(EPOCH FROM NOW()), + EXTRACT(EPOCH FROM NOW()) +); diff --git a/tests/_helpers/TestUserModel.php b/tests/_helpers/TestUserModel.php index b48bcc9..28e23ce 100644 --- a/tests/_helpers/TestUserModel.php +++ b/tests/_helpers/TestUserModel.php @@ -3,6 +3,8 @@ namespace Yii2Oauth2ServerTests\_helpers; use rhertogh\Yii2Oauth2Server\interfaces\models\external\user\Oauth2UserInterface; +use rhertogh\Yii2Oauth2Server\models\behaviors\BooleanBehavior; +use rhertogh\Yii2Oauth2Server\models\behaviors\TimestampBehavior; use rhertogh\Yii2Oauth2Server\Oauth2Module; use rhertogh\Yii2Oauth2Server\traits\models\Oauth2UserIdentityTrait; use Yii; @@ -15,6 +17,21 @@ class TestUserModel extends ActiveRecord implements { use Oauth2UserIdentityTrait; # Helper trait to pass IdentityInterface methods to Oauth2UserInterface + /** + * @inheritdoc + */ + public function behaviors() + { + return [ + 'timestampBehavior' => [ + 'class' => TimestampBehavior::class, + 'createdAtAttribute' => 'created_at', + 'updatedAtAttribute' => 'updated_at', + ], + 'booleanBehavior' => BooleanBehavior::class, + ]; + } + /** * @inheritDoc */ diff --git a/tests/_helpers/fixtures/BaseDbFixture.php b/tests/_helpers/fixtures/BaseDbFixture.php index 1b046a2..2efb59d 100644 --- a/tests/_helpers/fixtures/BaseDbFixture.php +++ b/tests/_helpers/fixtures/BaseDbFixture.php @@ -13,7 +13,8 @@ abstract class BaseDbFixture extends \yii\test\InitDbFixture public function init() { - $this->db = new Connection(DatabaseFixtures::getDbConfig($this->driverName)['connection']); + $connectionConfig = DatabaseFixtures::getDbConfig($this->driverName)['connection']; + $this->db = new Connection($connectionConfig); Yii::$app->setComponents([ 'db' => $this->db, ]); @@ -25,6 +26,7 @@ public function load() if (static::$_created !== static::class) { $this->createDbFixtures(); static::$_created = static::class; + $this->db->getSchema()->refresh(); } } diff --git a/tests/_helpers/fixtures/DatabaseFixtures.php b/tests/_helpers/fixtures/DatabaseFixtures.php index c7608c7..4fa65d6 100644 --- a/tests/_helpers/fixtures/DatabaseFixtures.php +++ b/tests/_helpers/fixtures/DatabaseFixtures.php @@ -4,6 +4,7 @@ use Yii; use yii\base\Module; +use yii\console\ExitCode; use yii\db\Exception; use yii\helpers\ArrayHelper; use Yii2Oauth2ServerTests\_helpers\EchoMigrateController; @@ -18,16 +19,16 @@ public static function createDbFixtures( $runMigrations = true, $runPostMigrationsFixtures = true ) { - $pdo_database = 'pdo_' . $driverName; - if ($driverName === 'oci') { - $pdo_database = 'oci8'; - } + $dbConfig = static::getDbConfig($driverName); - if (!\extension_loaded('pdo') || !\extension_loaded($pdo_database)) { - throw new Exception('pdo and ' . $pdo_database . ' extension are required.'); - } + $pdoDriver = 'pdo_' . explode(':', $dbConfig['connection']['dsn'])[0]; +// if ($driverName === 'oci') { +// $pdoDriver = 'oci8'; +// } - $dbConfig = static::getDbConfig($driverName); + if (!\extension_loaded('pdo') || !\extension_loaded($pdoDriver)) { + throw new Exception('pdo and ' . $pdoDriver . ' extension are required.'); + } if ($runPreMigrationsFixtures && !empty($dbConfig['preMigrationsFixtures'])) { foreach ($dbConfig['preMigrationsFixtures'] as $preMigrationsFixture) { @@ -107,9 +108,15 @@ protected static function runMigrations($config): void ob_start(); ob_implicit_flush(false); - $migrateController->run('up'); + $result = $migrateController->run('up'); + + if ($result !== ExitCode::OK) { + ob_end_flush(); + throw new Exception('Migration(s) failed.'); + } ob_end_clean(); + } catch (\Exception $e) { ob_end_flush(); throw $e; diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 00d0bd8..550f1f4 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -11,6 +11,7 @@ services: - test depends_on: - MySql_5_7 + - Postgres_14 ports: - '${DOCKER_HOST_HTTP_PORT:-88}:80' # http port mapping, it's recommended to use a different port than the default sample app. volumes: @@ -28,11 +29,17 @@ services: - YII2_OAUTH2_SERVER_PRIVATE_KEY_PASSPHRASE=${YII2_OAUTH2_SERVER_PRIVATE_KEY_PASSPHRASE:?err} - YII2_OAUTH2_SERVER_CODES_ENCRYPTION_KEY=${YII2_OAUTH2_SERVER_CODES_ENCRYPTION_KEY:?err} - YII2_OAUTH2_SERVER_STORAGE_ENCRYPTION_KEYS=${YII2_OAUTH2_SERVER_STORAGE_ENCRYPTION_KEYS:?err} - # Database - - MYSQL_HOST=${MYSQL_HOST:?err} + # MySQL - MYSQL_DB_NAME=${MYSQL_DB_NAME:?err} - MYSQL_USER_NAME=${MYSQL_USER_NAME:?err} - MYSQL_USER_PASSWORD=${MYSQL_USER_PASSWORD:?err} + # Postgres + - POSTGRES_USER=${POSTGRES_USER:?err} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?err} + - POSTGRES_DB=${POSTGRES_DB:?err} + # Docker Hosts + - MYSQL_HOST=${MYSQL_HOST:?err} + - POSTGRES_HOST=${POSTGRES_HOST:?err} extra_hosts: - host.docker.internal:host-gateway @@ -49,6 +56,18 @@ services: ports: - "${DOCKER_HOST_MYSQL_PORT:-3307}:3306" # mysql port mapping, it's recommended to use a different port than the default sample app. + Postgres_14: + image: "postgres:14-alpine" + restart: always + environment: + - POSTGRES_USER=${POSTGRES_USER:?err} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?err} + - POSTGRES_DB=${POSTGRES_DB:?err} + networks: + - test + ports: + - "${DOCKER_HOST_POSTGRES_PORT:-5433}:5432" # Postgres port mapping, it's recommended to use a different port than the default sample app. + networks: test: driver: bridge diff --git a/tests/functional/_base/BaseGrantCest.php b/tests/functional/_base/BaseGrantCest.php index 2f5ebf1..f066f3e 100644 --- a/tests/functional/_base/BaseGrantCest.php +++ b/tests/functional/_base/BaseGrantCest.php @@ -82,7 +82,7 @@ protected function testAuthenticatedRequest(ApiTester $I, $authToken, $expectedP 'id' => ['enum' => [123]], 'username' => ['enum' => ['test.user']], 'email_address' => ['enum' => ['test.user@test.test']], - 'enabled' => ['enum' => [1]], + 'enabled' => ['enum' => [true]], 'created_at' => ['type' => 'integer', 'minimum' => 1609455600], 'updated_at' => ['type' => 'integer', 'minimum' => 1609455600], ]; diff --git a/tests/unit/DatabaseTestCase.php b/tests/unit/DatabaseTestCase.php index 10efa34..4d43b61 100644 --- a/tests/unit/DatabaseTestCase.php +++ b/tests/unit/DatabaseTestCase.php @@ -15,10 +15,6 @@ abstract class DatabaseTestCase extends TestCase { - /** - * @var string the driver name of this test class. Must be set by a subclass. - */ - protected $driverName = 'mysql'; /** * @throws \yii\db\Exception diff --git a/tests/unit/components/authorization/base/Oauth2BaseClientAuthorizationRequestTest.php b/tests/unit/components/authorization/base/Oauth2BaseClientAuthorizationRequestTest.php index 19603b6..318f73a 100644 --- a/tests/unit/components/authorization/base/Oauth2BaseClientAuthorizationRequestTest.php +++ b/tests/unit/components/authorization/base/Oauth2BaseClientAuthorizationRequestTest.php @@ -6,12 +6,12 @@ use rhertogh\Yii2Oauth2Server\interfaces\models\external\user\Oauth2UserInterface; use rhertogh\Yii2Oauth2Server\Oauth2Module; use Yii2Oauth2ServerTests\_helpers\TestUserModel; -use Yii2Oauth2ServerTests\unit\TestCase; +use Yii2Oauth2ServerTests\unit\DatabaseTestCase; /** * @covers \rhertogh\Yii2Oauth2Server\components\authorization\base\Oauth2BaseClientAuthorizationRequest */ -class Oauth2BaseClientAuthorizationRequestTest extends TestCase +class Oauth2BaseClientAuthorizationRequestTest extends DatabaseTestCase { public function testGetSetModule() { diff --git a/tests/unit/components/repositories/Oauth2ScopeRepositoryTest.php b/tests/unit/components/repositories/Oauth2ScopeRepositoryTest.php index e965b98..982a1c3 100644 --- a/tests/unit/components/repositories/Oauth2ScopeRepositoryTest.php +++ b/tests/unit/components/repositories/Oauth2ScopeRepositoryTest.php @@ -154,14 +154,14 @@ public function finalizeScopesProvider() 'user.id.read', 'user.username.read', 'user.email_address.read', + 'defined-but-not-assigned', 'applied-by-default', 'applied-by-default-for-client', 'applied-automatically-by-default', 'applied-automatically-by-default-for-client', - 'applied-by-default-by-client-not-required-for-client', - 'defined-but-not-assigned', 'applied-automatically-by-default-not-assigned', 'applied-automatically-by-default-not-assigned-not-required', + 'applied-by-default-by-client-not-required-for-client', ], ], [ diff --git a/tests/unit/filters/auth/Oauth2HttpBearerAuthTest.php b/tests/unit/filters/auth/Oauth2HttpBearerAuthTest.php index 1c08aa8..b2316fe 100644 --- a/tests/unit/filters/auth/Oauth2HttpBearerAuthTest.php +++ b/tests/unit/filters/auth/Oauth2HttpBearerAuthTest.php @@ -8,12 +8,12 @@ use yii\base\Module; use yii\web\UnauthorizedHttpException; use Yii2Oauth2ServerTests\_helpers\TestUserModel; -use Yii2Oauth2ServerTests\unit\TestCase; +use Yii2Oauth2ServerTests\unit\DatabaseTestCase; /** * @covers \rhertogh\Yii2Oauth2Server\filters\auth\Oauth2HttpBearerAuth */ -class Oauth2HttpBearerAuthTest extends TestCase +class Oauth2HttpBearerAuthTest extends DatabaseTestCase { public function testAuthenticate() { diff --git a/tests/unit/migrations/CreateOauth2TablesMigrationTest.php b/tests/unit/migrations/CreateOauth2TablesMigrationTest.php index 06cb0b8..31b5aff 100644 --- a/tests/unit/migrations/CreateOauth2TablesMigrationTest.php +++ b/tests/unit/migrations/CreateOauth2TablesMigrationTest.php @@ -4,6 +4,7 @@ use rhertogh\Yii2Oauth2Server\migrations\Oauth2_00001_CreateOauth2TablesMigration; use rhertogh\Yii2Oauth2Server\Oauth2Module; +use yii\db\ColumnSchema; use yii\db\TableSchema; use Yii2Oauth2ServerTests\_helpers\TestUserModel; use Yii2Oauth2ServerTests\unit\migrations\_base\BaseMigrationTest; @@ -51,6 +52,12 @@ public static function getTableSchema() { return new TableSchema([ 'primaryKey' => ['column_a', 'column_b'], + 'columns' => [ + 'column_a' => new ColumnSchema(['type' => 'integer']), + 'column_b' => new ColumnSchema(['type' => 'integer']), + 'created_at' => new ColumnSchema(['type' => 'integer']), + 'updated_at' => new ColumnSchema(['type' => 'integer']), + ], ]); } }), @@ -72,6 +79,17 @@ public static function getTableSchema() { return false; } + + public function behaviors() + { + return array_diff_key( + parent::behaviors(), + array_flip([ + 'timestampBehavior', + 'booleanBehavior', + ]) + ); + } }), ], ], diff --git a/tests/unit/migrations/InsertOpenIdConnectScopesMigrationTest.php b/tests/unit/migrations/InsertOpenIdConnectScopesMigrationTest.php index 190a8bf..425f11e 100644 --- a/tests/unit/migrations/InsertOpenIdConnectScopesMigrationTest.php +++ b/tests/unit/migrations/InsertOpenIdConnectScopesMigrationTest.php @@ -26,7 +26,7 @@ public function dependsOnMigrations() } /** - * @param $enableOpenIdConnect + * @param bool $enableOpenIdConnect * @dataProvider generationIsActiveProvider */ public function testGenerationIsActive($enableOpenIdConnect) diff --git a/tests/unit/migrations/_base/BaseMigrationTest.php b/tests/unit/migrations/_base/BaseMigrationTest.php index e259342..7c619ce 100644 --- a/tests/unit/migrations/_base/BaseMigrationTest.php +++ b/tests/unit/migrations/_base/BaseMigrationTest.php @@ -39,10 +39,7 @@ public function getMigrationClassWrapper($migrationClass) return $wrapperClass; } - /** - * - */ - public function testSaveUpDown() + public function testSafeUpDown() { $this->runDependentMigration(); $migrationClass = $this->getMigrationClassWrapper($this->getMigrationClass()); diff --git a/tests/unit/models/Oauth2ClientTest.php b/tests/unit/models/Oauth2ClientTest.php index c3f6b73..a01c7e5 100644 --- a/tests/unit/models/Oauth2ClientTest.php +++ b/tests/unit/models/Oauth2ClientTest.php @@ -505,9 +505,9 @@ public function getAllowedScopesProvider() 'applied-by-default-for-client', 'applied-automatically-by-default', 'applied-automatically-by-default-for-client', - 'applied-by-default-by-client-not-required-for-client', 'applied-automatically-by-default-not-assigned', 'applied-automatically-by-default-not-assigned-not-required', + 'applied-by-default-by-client-not-required-for-client', ], ], [ @@ -547,14 +547,14 @@ public function getAllowedScopesProvider() 'user.id.read', 'user.username.read', 'user.email_address.read', + 'defined-but-not-assigned', 'applied-by-default', 'applied-by-default-for-client', 'applied-automatically-by-default', 'applied-automatically-by-default-for-client', - 'applied-by-default-by-client-not-required-for-client', - 'defined-but-not-assigned', 'applied-automatically-by-default-not-assigned', 'applied-automatically-by-default-not-assigned-not-required', + 'applied-by-default-by-client-not-required-for-client', ], ], ]; diff --git a/tests/unit/module/base/Oauth2BaseModuleTest.php b/tests/unit/module/base/Oauth2BaseModuleTest.php index 35e2251..a073834 100644 --- a/tests/unit/module/base/Oauth2BaseModuleTest.php +++ b/tests/unit/module/base/Oauth2BaseModuleTest.php @@ -22,12 +22,12 @@ use Yii; use Yii2Oauth2ServerTests\_helpers\TestUserModel; use Yii2Oauth2ServerTests\_helpers\TestUserModelOidc; -use Yii2Oauth2ServerTests\unit\TestCase; +use Yii2Oauth2ServerTests\unit\DatabaseTestCase; /** * @covers \rhertogh\Yii2Oauth2Server\base\Oauth2BaseModule */ -class Oauth2BaseModuleTest extends TestCase +class Oauth2BaseModuleTest extends DatabaseTestCase { public function testGetGrantTypeId() { diff --git a/tests/unit/traits/Oauth2OidcUserIdentityTraitTest.php b/tests/unit/traits/Oauth2OidcUserIdentityTraitTest.php index cc183d0..a0c9ff1 100644 --- a/tests/unit/traits/Oauth2OidcUserIdentityTraitTest.php +++ b/tests/unit/traits/Oauth2OidcUserIdentityTraitTest.php @@ -11,12 +11,12 @@ use rhertogh\Yii2Oauth2Server\traits\models\Oauth2OidcUserIdentityTrait; use yii\base\Component; use Yii2Oauth2ServerTests\_helpers\TestUserModelOidc; -use Yii2Oauth2ServerTests\unit\TestCase; +use Yii2Oauth2ServerTests\unit\DatabaseTestCase; /** * @covers \rhertogh\Yii2Oauth2Server\traits\models\Oauth2OidcUserIdentityTrait */ -class Oauth2OidcUserIdentityTraitTest extends TestCase +class Oauth2OidcUserIdentityTraitTest extends DatabaseTestCase { public function testGetOpenIdConnectClaimValue() {