diff --git a/.gitattributes b/.gitattributes
index efd7a3a8e..6c56d0f01 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -14,5 +14,6 @@
.gitignore export-ignore
.styleci.yml export-ignore
CHANGELOG.md export-ignore
+phpstan.neon.dist export-ignore
phpunit.xml.dist export-ignore
UPGRADE.md
diff --git a/.github/ISSUE_TEMPLATE/1_Bug_report.md b/.github/ISSUE_TEMPLATE/1_Bug_report.md
deleted file mode 100644
index 2d7b68a23..000000000
--- a/.github/ISSUE_TEMPLATE/1_Bug_report.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-name: "Bug report"
-about: "Report something that's broken. Please ensure your Laravel version is still supported: https://laravel.com/docs/releases#support-policy"
----
-
-
-
-
-- Passport Version: #.#.#
-- Laravel Version: #.#.#
-- PHP Version: #.#.#
-- Database Driver & Version:
-
-### Description:
-
-
-### Steps To Reproduce:
-
-
-
diff --git a/.github/ISSUE_TEMPLATE/1_Bug_report.yml b/.github/ISSUE_TEMPLATE/1_Bug_report.yml
new file mode 100644
index 000000000..bc02d3a13
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/1_Bug_report.yml
@@ -0,0 +1,47 @@
+name: Bug Report
+description: "Report a general library issue."
+body:
+ - type: markdown
+ attributes:
+ value: "Before submitting your report, [please ensure your Laravel version is still supported](https://laravel.com/docs/releases#support-policy)."
+ - type: input
+ attributes:
+ label: Passport Version
+ description: Provide the Passport version that you are using.
+ placeholder: 10.0.1
+ validations:
+ required: true
+ - type: input
+ attributes:
+ label: Laravel Version
+ description: Provide the Laravel version that you are using.
+ placeholder: 10.4.1
+ validations:
+ required: true
+ - type: input
+ attributes:
+ label: PHP Version
+ description: Provide the PHP version that you are using.
+ placeholder: 8.1.4
+ validations:
+ required: true
+ - type: input
+ attributes:
+ label: Database Driver & Version
+ description: If applicable, provide the database driver and version you are using.
+ placeholder: "MySQL 8.0.31 for macOS 13.0 on arm64 (Homebrew)"
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Description
+ description: Provide a detailed description of the issue you are facing.
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Steps To Reproduce
+ description: Provide detailed steps to reproduce your issue. If necessary, please provide a GitHub repository to demonstrate your issue using `laravel new bug-report --github="--public"`.
+ validations:
+ required: true
+
diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml
new file mode 100644
index 000000000..9634a0edb
--- /dev/null
+++ b/.github/workflows/issues.yml
@@ -0,0 +1,12 @@
+name: issues
+
+on:
+ issues:
+ types: [labeled]
+
+permissions:
+ issues: write
+
+jobs:
+ help-wanted:
+ uses: laravel/.github/.github/workflows/issues.yml@main
diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
new file mode 100644
index 000000000..efea805e1
--- /dev/null
+++ b/.github/workflows/static-analysis.yml
@@ -0,0 +1,41 @@
+name: static analysis
+
+on:
+ push:
+ branches:
+ - master
+ - '*.x'
+ pull_request:
+
+permissions:
+ contents: read
+
+jobs:
+ tests:
+ runs-on: ubuntu-22.04
+
+ strategy:
+ fail-fast: true
+
+ name: Static Analysis
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.2
+ tools: composer:v2
+ coverage: none
+
+ - name: Install dependencies
+ uses: nick-fields/retry@v2
+ with:
+ timeout_minutes: 5
+ max_attempts: 5
+ command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress
+
+ - name: Execute type checking
+ run: vendor/bin/phpstan
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 64514f889..3ae685569 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -2,30 +2,33 @@ name: tests
on:
push:
+ branches:
+ - master
+ - '*.x'
pull_request:
schedule:
- cron: '0 0 * * *'
jobs:
tests:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
strategy:
fail-fast: true
matrix:
- php: [7.3, 7.4, '8.0', 8.1]
- laravel: [8, 9]
+ php: ['8.0', 8.1, 8.2, 8.3]
+ laravel: [9, 10]
exclude:
- - php: 7.3
- laravel: 9
- - php: 7.4
+ - php: '8.0'
+ laravel: 10
+ - php: 8.3
laravel: 9
name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }}
steps:
- name: Checkout code
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 543b8f20b..c2a573833 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,177 @@
# Release Notes
-## [Unreleased](https://github.com/laravel/passport/compare/v10.4.1...10.x)
+## [Unreleased](https://github.com/laravel/passport/compare/v11.10.0...11.x)
+
+## [v11.10.0](https://github.com/laravel/passport/compare/v11.9.2...v11.10.0) - 2023-11-02
+
+- [11.x] Named static methods for middleware by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/passport/pull/1695
+- Simplify Conditional Statement by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/passport/pull/1696
+
+## [v11.9.2](https://github.com/laravel/passport/compare/v11.9.1...v11.9.2) - 2023-10-16
+
+- Add return to revokeRefreshTokensByAccessTokenId method by [@aminkhoshzahmat](https://github.com/aminkhoshzahmat) in https://github.com/laravel/passport/pull/1693
+
+## [v11.9.1](https://github.com/laravel/passport/compare/v11.9.0...v11.9.1) - 2023-09-01
+
+- [11.x] Allow scope repository to be constructed without parameters by [@axlon](https://github.com/axlon) in https://github.com/laravel/passport/pull/1686
+
+## [v11.9.0](https://github.com/laravel/passport/compare/v11.8.8...v11.9.0) - 2023-08-29
+
+- [11.x] Add the ability to limit scopes by client by [@axlon](https://github.com/axlon) in https://github.com/laravel/passport/pull/1682
+- [11.x] Add support for inherited scopes when limiting scopes on clients by [@axlon](https://github.com/axlon) in https://github.com/laravel/passport/pull/1683
+
+## [v11.8.8](https://github.com/laravel/passport/compare/v11.8.7...v11.8.8) - 2023-07-07
+
+- Add generics to client factory by [@axlon](https://github.com/axlon) in https://github.com/laravel/passport/pull/1669
+- Update composer.json by [@Smoggert](https://github.com/Smoggert) in https://github.com/laravel/passport/pull/1674
+- Update composer.json by [@drhoussem](https://github.com/drhoussem) in https://github.com/laravel/passport/pull/1677
+
+## [v11.8.7](https://github.com/laravel/passport/compare/v11.8.6...v11.8.7) - 2023-04-28
+
+- Revert "[11.x] Add Provider Guard to ClientRepository for Personal Access Clients" by @driesvints in https://github.com/laravel/passport/pull/1658
+
+## [v11.8.6](https://github.com/laravel/passport/compare/v11.8.5...v11.8.6) - 2023-04-24
+
+- Add Provider Guard to ClientRepository for Personal Access Clients by @michaelnabil230 in https://github.com/laravel/passport/pull/1655
+
+## [v11.8.5](https://github.com/laravel/passport/compare/v11.8.4...v11.8.5) - 2023-04-04
+
+- Allow `lcobucci/jwt` v5 and cleaned up version constraints by @GrahamCampbell in https://github.com/laravel/passport/pull/1649
+- Pass user identifier through to finalize scopes in personal access grant by @GrahamCampbell in https://github.com/laravel/passport/pull/1650
+
+## [v11.8.4](https://github.com/laravel/passport/compare/v11.8.3...v11.8.4) - 2023-03-18
+
+- Removed deprecated `dates` property from `RefreshToken` model by @siarheipashkevich in https://github.com/laravel/passport/pull/1645
+- Removed deprecated `dates` property from `AuthCode` model by @siarheipashkevich in https://github.com/laravel/passport/pull/1644
+- Fix doc block types by @hafezdivandari in https://github.com/laravel/passport/pull/1647
+
+## [v11.8.3](https://github.com/laravel/passport/compare/v11.8.2...v11.8.3) - 2023-03-01
+
+- Allow overriding the `AccessToken` class by @hafezdivandari in https://github.com/laravel/passport/pull/1638
+- Make `$userId` nullable in `ClientRepository->createPersonalAccessClient` by @bram-pkg in https://github.com/laravel/passport/pull/1642
+
+## [v11.8.2](https://github.com/laravel/passport/compare/v11.8.1...v11.8.2) - 2023-02-20
+
+- Re-apply "Added AuthenticationException to extend the behaviour of Laravel's default exception handler" by @driesvints in https://github.com/laravel/passport/commit/67c3e336af163f6eba5dbca8e5db46275ff0e433
+
+## [v11.8.1](https://github.com/laravel/passport/compare/v11.8.0...v11.8.1) - 2023-02-20
+
+- Revert "Move AuthenticationException into the scope of Laravel Passport" by @driesvints in https://github.com/laravel/passport/commit/db543b0cc13ed3f56f1bffda04707fbe2a8c7ab5
+
+## [v11.8.0](https://github.com/laravel/passport/compare/v11.7.0...v11.8.0) - 2023-02-17
+
+- Move AuthenticationException into the scope of Laravel Passport by @chrispage1 in https://github.com/laravel/passport/pull/1633
+- Custom authorization view response by @JonErickson in https://github.com/laravel/passport/pull/1629
+- Fix deprecated $dates property by @TonyWong9527 in https://github.com/laravel/passport/pull/1636
+
+## [v11.7.0](https://github.com/laravel/passport/compare/v11.6.1...v11.7.0) - 2023-02-08
+
+### Added
+
+- Add support for `EncryptCookies` middleware by @axlon in https://github.com/laravel/passport/pull/1628
+
+## [v11.6.1](https://github.com/laravel/passport/compare/v11.6.0...v11.6.1) - 2023-02-03
+
+### Changed
+
+- Indicate current token can be `TransientToken` by @axlon in https://github.com/laravel/passport/pull/1627
+
+## [v11.6.0](https://github.com/laravel/passport/compare/v11.5.1...v11.6.0) - 2023-01-31
+
+### Changed
+
+- Update ClientCommand.php's user_id description by @Smoggert in https://github.com/laravel/passport/pull/1619
+- Get model PK instead of forcibly id column by @lucaspanik in https://github.com/laravel/passport/pull/1626
+
+### Fixed
+
+- Fix doc block for `withAccessToken()` by @axlon in https://github.com/laravel/passport/pull/1620
+
+## [v11.5.1](https://github.com/laravel/passport/compare/v11.5.0...v11.5.1) - 2023-01-16
+
+### Fixed
+
+- Get authenticated user from the guard by @hafezdivandari in https://github.com/laravel/passport/pull/1617
+
+## [v11.5.0](https://github.com/laravel/passport/compare/v11.4.0...v11.5.0) - 2023-01-09
+
+### Added
+
+- Laravel v10 Support by @driesvints in https://github.com/laravel/passport/pull/1615
+
+## [v11.4.0](https://github.com/laravel/passport/compare/v11.3.1...v11.4.0) - 2023-01-03
+
+### Changed
+
+- Uses PHP Native Type Declarations 🐘 by @nunomaduro in https://github.com/laravel/passport/pull/1594
+
+## [v11.3.1](https://github.com/laravel/passport/compare/v11.3.0...v11.3.1) - 2022-12-02
+
+### Changed
+
+- Add auth guard to routes by @hafezdivandari in https://github.com/laravel/passport/pull/1603
+
+## [v11.3.0](https://github.com/laravel/passport/compare/v11.2.1...v11.3.0) - 2022-10-22
+
+### Added
+
+- Support prompting login when redirecting for authorization by @hafezdivandari in https://github.com/laravel/passport/pull/1577
+
+### Changed
+
+- Update PurgeCommand.php by @fatoskurtishi in https://github.com/laravel/passport/pull/1586
+- Fix ClientRepository doc blocks by @axlon in https://github.com/laravel/passport/pull/1587
+- Update docblock by @mnabialek in https://github.com/laravel/passport/pull/1588
+
+## [v11.2.1](https://github.com/laravel/passport/compare/v11.2.0...v11.2.1) - 2022-09-29
+
+### Fixed
+
+- Improve token guard return type by @axlon in https://github.com/laravel/passport/pull/1579
+
+## [v11.2.0](https://github.com/laravel/passport/compare/v11.1.0...v11.2.0) - 2022-09-07
+
+### Changed
+
+- Let OAuth2 server handle the denying response by @hafezdivandari in https://github.com/laravel/passport/pull/1572
+
+## [v11.1.0](https://github.com/laravel/passport/compare/v11.0.1...v11.1.0) - 2022-09-05
+
+### Added
+
+- Support prompting re-consent when redirecting for authorization by @hafezdivandari in https://github.com/laravel/passport/pull/1567
+- Support disabling prompt when redirecting for authorization by @hafezdivandari in https://github.com/laravel/passport/pull/1569
+
+## [v11.0.1](https://github.com/laravel/passport/compare/v11.0.0...v11.0.1) - 2022-08-29
+
+### Changed
+
+- Custom days and hours to passport purge command by @rubengg86 in https://github.com/laravel/passport/pull/1563
+- Allow for bootstrapping without loading routes by @axlon in https://github.com/laravel/passport/pull/1564
+
+## [v11.0.0](https://github.com/laravel/passport/compare/v10.4.1...v11.0.0) - 2022-08-19
+
+### Added
+
+- Allow authenticated client to be retrieved from the guard by @axlon in https://github.com/laravel/passport/pull/1508
+
+### Changed
+
+- Revert model DB connection customization by @driesvints in https://github.com/laravel/passport/pull/1412
+- Allow timestamps on Token model by @driesvints in https://github.com/laravel/passport/pull/1425
+- Improve authenticateViaBearerToken() performance by @alecpl in https://github.com/laravel/passport/pull/1447
+- Refactor routes to dedicated file by @driesvints in https://github.com/laravel/passport/pull/1464
+
+### Fixed
+
+- Stub client on guard when calling Passport::actingAsClient() by @axlon in https://github.com/laravel/passport/pull/1519
+- Fix scope inheritance when using Passport::actingAs() by @axlon in https://github.com/laravel/passport/pull/1551
+
+### Removed
+
+- Drop PHP 7.x and Laravel v8 by @driesvints in https://github.com/laravel/passport/pull/1558
+- Remove deprecated properties by @driesvints in https://github.com/laravel/passport/pull/1560
+- Remove deprecated functionality and simplify some feature tests by @driesvints in https://github.com/laravel/passport/pull/1559
## [v10.4.1](https://github.com/laravel/passport/compare/v10.4.0...v10.4.1) - 2022-04-16
diff --git a/UPGRADE.md b/UPGRADE.md
index 4df35ceb5..643f7640d 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -2,6 +2,50 @@
## General Notes
+## Upgrading To 11.0 From 10.x
+
+### Minimum PHP Version
+
+PHP 8.0 is now the minimum required version.
+
+### Minimum Laravel Version
+
+Laravel 9.0 is now the minimum required version.
+
+### Reverting Model DB Connection Customization
+
+PR: https://github.com/laravel/passport/pull/1412
+
+Customizing model database connections through the migration files has been reverted. This was first introduced in [this PR](https://github.com/laravel/passport/pull/1255).
+
+If you need to customize the database connection for a model you should override the models [as explained in the documentation](https://laravel.com/docs/9.x/passport#overriding-default-models).
+
+### Allow Timestamps On Token model
+
+PR: https://github.com/laravel/passport/pull/1425
+
+Timestamps are now allowed on the `Token` model. If you specifically didn't want these model's timestamps to be updated then you may override the `Token` model [as explained in the documentation](https://laravel.com/docs/9.x/passport#overriding-default-models).
+
+### Refactor Routes To Dedicated File
+
+PR: https://github.com/laravel/passport/pull/1464
+
+Passport's routes have been moved to a dedicated route file. You can remove the `Passport::routes()` call from your application's service provider.
+
+If you previously relied on overwriting routes using `routes($callback = null, array $options = [])` you may now achieve the same behavior by simply overwriting the routes in your application's own `web.php` route file.
+
+### Stubbing Client In Tests
+
+PR: https://github.com/laravel/passport/pull/1519
+
+Previously, a stubbed client created via `Passport::actingAsClient(...)` wasn't retrieved when calling the `->client()` method on the API guard. This has been fixed in Passport v11 to reflect real-world situations and you may need to accommodate for this behavior in your tests.
+
+### Scope Inheritance In Tests
+
+PR: https://github.com/laravel/passport/pull/1551
+
+Previously, scopes weren't inherited when using `Passport::actingAs(...)`. This has been fixed in Passport v11 to reflect real-world situations and you may need to accommodate for this behavior in your tests.
+
## Upgrading To 10.0 From 9.x
### Minimum PHP Version
diff --git a/composer.json b/composer.json
index ccc2f768f..16ec45c82 100644
--- a/composer.json
+++ b/composer.json
@@ -14,27 +14,28 @@
}
],
"require": {
- "php": "^7.3|^8.0",
+ "php": "^8.0",
"ext-json": "*",
- "firebase/php-jwt": "^6.0",
- "illuminate/auth": "^8.37|^9.0",
- "illuminate/console": "^8.37|^9.0",
- "illuminate/container": "^8.37|^9.0",
- "illuminate/contracts": "^8.37|^9.0",
- "illuminate/cookie": "^8.37|^9.0",
- "illuminate/database": "^8.37|^9.0",
- "illuminate/encryption": "^8.37|^9.0",
- "illuminate/http": "^8.37|^9.0",
- "illuminate/support": "^8.37|^9.0",
- "lcobucci/jwt": "^3.4|^4.0",
+ "firebase/php-jwt": "^6.4",
+ "illuminate/auth": "^9.0|^10.0",
+ "illuminate/console": "^9.0|^10.0",
+ "illuminate/container": "^9.0|^10.0",
+ "illuminate/contracts": "^9.0|^10.0",
+ "illuminate/cookie": "^9.0|^10.0",
+ "illuminate/database": "^9.0|^10.0",
+ "illuminate/encryption": "^9.0|^10.0",
+ "illuminate/http": "^9.0|^10.0",
+ "illuminate/support": "^9.0|^10.0",
+ "lcobucci/jwt": "^4.3|^5.0",
"league/oauth2-server": "dev-device-flow-grant-update2",
- "nyholm/psr7": "^1.3",
+ "nyholm/psr7": "^1.5",
"phpseclib/phpseclib": "^2.0|^3.0",
- "symfony/psr-http-message-bridge": "^2.0"
+ "symfony/psr-http-message-bridge": "^2.1"
},
"require-dev": {
"mockery/mockery": "^1.0",
- "orchestra/testbench": "^6.0|^7.0",
+ "orchestra/testbench": "^7.31|^8.11",
+ "phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.3"
},
"autoload": {
@@ -45,12 +46,14 @@
},
"autoload-dev": {
"psr-4": {
- "Laravel\\Passport\\Tests\\": "tests/"
+ "Laravel\\Passport\\Tests\\": "tests/",
+ "Workbench\\App\\": "workbench/app/",
+ "Workbench\\Database\\Factories\\": "workbench/database/factories/"
}
},
"extra": {
"branch-alias": {
- "dev-master": "10.x-dev"
+ "dev-master": "11.x-dev"
},
"laravel": {
"providers": [
@@ -61,6 +64,10 @@
"config": {
"sort-packages": true
},
+ "scripts": {
+ "post-autoload-dump": "@prepare",
+ "prepare": "@php vendor/bin/testbench package:discover --ansi"
+ },
"minimum-stability": "dev",
"prefer-stable": true,
"repositories": [
diff --git a/config/passport.php b/config/passport.php
index 64ba5f082..06053cd12 100644
--- a/config/passport.php
+++ b/config/passport.php
@@ -2,6 +2,19 @@
return [
+ /*
+ |--------------------------------------------------------------------------
+ | Passport Guard
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify which authentication guard Passport will use when
+ | authenticating users. This value should correspond with one of your
+ | guards that is already present in your "auth" configuration file.
+ |
+ */
+
+ 'guard' => 'web',
+
/*
|--------------------------------------------------------------------------
| Encryption Keys
@@ -46,20 +59,4 @@
'secret' => env('PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET'),
],
- /*
- |--------------------------------------------------------------------------
- | Passport Storage Driver
- |--------------------------------------------------------------------------
- |
- | This configuration value allows you to customize the storage options
- | for Passport, such as the database connection that should be used
- | by Passport's internal database models which store tokens, etc.
- |
- */
-
- 'storage' => [
- 'database' => [
- 'connection' => env('DB_CONNECTION', 'mysql'),
- ],
- ],
];
diff --git a/database/factories/ClientFactory.php b/database/factories/ClientFactory.php
index 07796191b..eec50f041 100644
--- a/database/factories/ClientFactory.php
+++ b/database/factories/ClientFactory.php
@@ -7,6 +7,9 @@
use Laravel\Passport\Client;
use Laravel\Passport\Passport;
+/**
+ * @extends \Illuminate\Database\Eloquent\Factories\Factory<\Laravel\Passport\Client>
+ */
class ClientFactory extends Factory
{
/**
diff --git a/database/migrations/2016_06_01_000001_create_oauth_auth_codes_table.php b/database/migrations/2016_06_01_000001_create_oauth_auth_codes_table.php
index 195685f7a..7b93b406a 100644
--- a/database/migrations/2016_06_01_000001_create_oauth_auth_codes_table.php
+++ b/database/migrations/2016_06_01_000001_create_oauth_auth_codes_table.php
@@ -6,31 +6,12 @@
return new class extends Migration
{
- /**
- * The database schema.
- *
- * @var \Illuminate\Database\Schema\Builder
- */
- protected $schema;
-
- /**
- * Create a new migration instance.
- *
- * @return void
- */
- public function __construct()
- {
- $this->schema = Schema::connection($this->getConnection());
- }
-
/**
* Run the migrations.
- *
- * @return void
*/
- public function up()
+ public function up(): void
{
- $this->schema->create('oauth_auth_codes', function (Blueprint $table) {
+ Schema::create('oauth_auth_codes', function (Blueprint $table) {
$table->string('id', 100)->primary();
$table->unsignedBigInteger('user_id')->index();
$table->unsignedBigInteger('client_id');
@@ -42,21 +23,9 @@ public function up()
/**
* Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- $this->schema->dropIfExists('oauth_auth_codes');
- }
-
- /**
- * Get the migration connection name.
- *
- * @return string|null
*/
- public function getConnection()
+ public function down(): void
{
- return config('passport.storage.database.connection');
+ Schema::dropIfExists('oauth_auth_codes');
}
};
diff --git a/database/migrations/2016_06_01_000002_create_oauth_access_tokens_table.php b/database/migrations/2016_06_01_000002_create_oauth_access_tokens_table.php
index c8ecd7227..598798eef 100644
--- a/database/migrations/2016_06_01_000002_create_oauth_access_tokens_table.php
+++ b/database/migrations/2016_06_01_000002_create_oauth_access_tokens_table.php
@@ -6,31 +6,12 @@
return new class extends Migration
{
- /**
- * The database schema.
- *
- * @var \Illuminate\Database\Schema\Builder
- */
- protected $schema;
-
- /**
- * Create a new migration instance.
- *
- * @return void
- */
- public function __construct()
- {
- $this->schema = Schema::connection($this->getConnection());
- }
-
/**
* Run the migrations.
- *
- * @return void
*/
- public function up()
+ public function up(): void
{
- $this->schema->create('oauth_access_tokens', function (Blueprint $table) {
+ Schema::create('oauth_access_tokens', function (Blueprint $table) {
$table->string('id', 100)->primary();
$table->unsignedBigInteger('user_id')->nullable()->index();
$table->unsignedBigInteger('client_id');
@@ -44,21 +25,9 @@ public function up()
/**
* Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- $this->schema->dropIfExists('oauth_access_tokens');
- }
-
- /**
- * Get the migration connection name.
- *
- * @return string|null
*/
- public function getConnection()
+ public function down(): void
{
- return config('passport.storage.database.connection');
+ Schema::dropIfExists('oauth_access_tokens');
}
};
diff --git a/database/migrations/2016_06_01_000003_create_oauth_refresh_tokens_table.php b/database/migrations/2016_06_01_000003_create_oauth_refresh_tokens_table.php
index 998b63158..b007904ce 100644
--- a/database/migrations/2016_06_01_000003_create_oauth_refresh_tokens_table.php
+++ b/database/migrations/2016_06_01_000003_create_oauth_refresh_tokens_table.php
@@ -6,31 +6,12 @@
return new class extends Migration
{
- /**
- * The database schema.
- *
- * @var \Illuminate\Database\Schema\Builder
- */
- protected $schema;
-
- /**
- * Create a new migration instance.
- *
- * @return void
- */
- public function __construct()
- {
- $this->schema = Schema::connection($this->getConnection());
- }
-
/**
* Run the migrations.
- *
- * @return void
*/
- public function up()
+ public function up(): void
{
- $this->schema->create('oauth_refresh_tokens', function (Blueprint $table) {
+ Schema::create('oauth_refresh_tokens', function (Blueprint $table) {
$table->string('id', 100)->primary();
$table->string('access_token_id', 100)->index();
$table->boolean('revoked');
@@ -40,21 +21,9 @@ public function up()
/**
* Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- $this->schema->dropIfExists('oauth_refresh_tokens');
- }
-
- /**
- * Get the migration connection name.
- *
- * @return string|null
*/
- public function getConnection()
+ public function down(): void
{
- return config('passport.storage.database.connection');
+ Schema::dropIfExists('oauth_refresh_tokens');
}
};
diff --git a/database/migrations/2016_06_01_000004_create_oauth_clients_table.php b/database/migrations/2016_06_01_000004_create_oauth_clients_table.php
index 48b316dd7..fdfdb488f 100644
--- a/database/migrations/2016_06_01_000004_create_oauth_clients_table.php
+++ b/database/migrations/2016_06_01_000004_create_oauth_clients_table.php
@@ -4,43 +4,13 @@
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
-return new class extends Migration
-{
- /**
- * The database schema.
- *
- * @var \Illuminate\Database\Schema\Builder
- */
- protected $schema;
-
- /**
- * Create a new migration instance.
- *
- * @return void
- */
- public function __construct()
- {
- $this->schema = Schema::connection($this->getConnection());
- }
-
- /**
- * Get the migration connection name.
- *
- * @return string|null
- */
- public function getConnection()
- {
- return config('passport.storage.database.connection');
- }
-
+return new class extends Migration {
/**
* Run the migrations.
- *
- * @return void
*/
- public function up()
+ public function up(): void
{
- $this->schema->create('oauth_clients', function (Blueprint $table) {
+ Schema::create('oauth_clients', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id')->nullable()->index();
$table->string('name');
@@ -51,18 +21,15 @@ public function up()
$table->boolean('password_client');
$table->boolean('device_client');
$table->boolean('revoked');
- $table->string('user_provider_name')->nullable()->default(null);
$table->timestamps();
});
}
/**
* Reverse the migrations.
- *
- * @return void
*/
- public function down()
+ public function down(): void
{
- $this->schema->dropIfExists('oauth_clients');
+ Schema::dropIfExists('oauth_clients');
}
};
diff --git a/database/migrations/2016_06_01_000005_create_oauth_personal_access_clients_table.php b/database/migrations/2016_06_01_000005_create_oauth_personal_access_clients_table.php
index e12920ab7..7c9d1e8f1 100644
--- a/database/migrations/2016_06_01_000005_create_oauth_personal_access_clients_table.php
+++ b/database/migrations/2016_06_01_000005_create_oauth_personal_access_clients_table.php
@@ -6,31 +6,12 @@
return new class extends Migration
{
- /**
- * The database schema.
- *
- * @var \Illuminate\Database\Schema\Builder
- */
- protected $schema;
-
- /**
- * Create a new migration instance.
- *
- * @return void
- */
- public function __construct()
- {
- $this->schema = Schema::connection($this->getConnection());
- }
-
/**
* Run the migrations.
- *
- * @return void
*/
- public function up()
+ public function up(): void
{
- $this->schema->create('oauth_personal_access_clients', function (Blueprint $table) {
+ Schema::create('oauth_personal_access_clients', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('client_id');
$table->timestamps();
@@ -39,21 +20,9 @@ public function up()
/**
* Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- $this->schema->dropIfExists('oauth_personal_access_clients');
- }
-
- /**
- * Get the migration connection name.
- *
- * @return string|null
*/
- public function getConnection()
+ public function down(): void
{
- return config('passport.storage.database.connection');
+ Schema::dropIfExists('oauth_personal_access_clients');
}
};
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 000000000..649776a4c
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,11 @@
+parameters:
+ paths:
+ - config
+ - database
+ - routes
+ - src
+
+ level: 0
+
+ ignoreErrors:
+ - "#Unsafe usage of new static\\(\\)#"
diff --git a/resources/views/authorize.blade.php b/resources/views/authorize.blade.php
index ecbdcaa58..d0a4a991c 100644
--- a/resources/views/authorize.blade.php
+++ b/resources/views/authorize.blade.php
@@ -68,7 +68,7 @@
@csrf
-
+
@@ -79,7 +79,7 @@
@method('DELETE')
-
+
diff --git a/routes/web.php b/routes/web.php
new file mode 100644
index 000000000..c69d4ddb1
--- /dev/null
+++ b/routes/web.php
@@ -0,0 +1,90 @@
+ 'AccessTokenController@issueToken',
+ 'as' => 'token',
+ 'middleware' => 'throttle',
+]);
+
+Route::get('/authorize', [
+ 'uses' => 'AuthorizationController@authorize',
+ 'as' => 'authorizations.authorize',
+ 'middleware' => 'web',
+]);
+
+Route::post('/device_authorization', [
+ 'uses' => 'DeviceAuthorizationController@authorize',
+ 'as' => 'authorizations.authorize_device',
+ 'middleware' => 'throttle',
+]);
+
+$guard = config('passport.guard', null);
+
+Route::middleware(['web', $guard ? 'auth:'.$guard : 'auth'])->group(function () {
+ Route::post('/token/refresh', [
+ 'uses' => 'TransientTokenController@refresh',
+ 'as' => 'token.refresh',
+ ]);
+
+ Route::post('/authorize', [
+ 'uses' => 'ApproveAuthorizationController@approve',
+ 'as' => 'authorizations.approve',
+ ]);
+
+ Route::delete('/authorize', [
+ 'uses' => 'DenyAuthorizationController@deny',
+ 'as' => 'authorizations.deny',
+ ]);
+
+ Route::get('/tokens', [
+ 'uses' => 'AuthorizedAccessTokenController@forUser',
+ 'as' => 'tokens.index',
+ ]);
+
+ Route::delete('/tokens/{token_id}', [
+ 'uses' => 'AuthorizedAccessTokenController@destroy',
+ 'as' => 'tokens.destroy',
+ ]);
+
+ Route::get('/clients', [
+ 'uses' => 'ClientController@forUser',
+ 'as' => 'clients.index',
+ ]);
+
+ Route::post('/clients', [
+ 'uses' => 'ClientController@store',
+ 'as' => 'clients.store',
+ ]);
+
+ Route::put('/clients/{client_id}', [
+ 'uses' => 'ClientController@update',
+ 'as' => 'clients.update',
+ ]);
+
+ Route::delete('/clients/{client_id}', [
+ 'uses' => 'ClientController@destroy',
+ 'as' => 'clients.destroy',
+ ]);
+
+ Route::get('/scopes', [
+ 'uses' => 'ScopeController@all',
+ 'as' => 'scopes.index',
+ ]);
+
+ Route::get('/personal-access-tokens', [
+ 'uses' => 'PersonalAccessTokenController@forUser',
+ 'as' => 'personal.tokens.index',
+ ]);
+
+ Route::post('/personal-access-tokens', [
+ 'uses' => 'PersonalAccessTokenController@store',
+ 'as' => 'personal.tokens.store',
+ ]);
+
+ Route::delete('/personal-access-tokens/{token_id}', [
+ 'uses' => 'PersonalAccessTokenController@destroy',
+ 'as' => 'personal.tokens.destroy',
+ ]);
+});
diff --git a/src/AuthCode.php b/src/AuthCode.php
index 94441b5f7..c3b194ad7 100644
--- a/src/AuthCode.php
+++ b/src/AuthCode.php
@@ -34,15 +34,7 @@ class AuthCode extends Model
*/
protected $casts = [
'revoked' => 'bool',
- ];
-
- /**
- * The attributes that should be mutated to dates.
- *
- * @var array
- */
- protected $dates = [
- 'expires_at',
+ 'expires_at' => 'datetime',
];
/**
diff --git a/src/Bridge/AccessTokenRepository.php b/src/Bridge/AccessTokenRepository.php
index 0fe2be49b..23572d760 100644
--- a/src/Bridge/AccessTokenRepository.php
+++ b/src/Bridge/AccessTokenRepository.php
@@ -5,6 +5,7 @@
use DateTime;
use Illuminate\Contracts\Events\Dispatcher;
use Laravel\Passport\Events\AccessTokenCreated;
+use Laravel\Passport\Passport;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\ClientEntityInterface;
@@ -46,7 +47,7 @@ public function __construct(TokenRepository $tokenRepository, Dispatcher $events
*/
public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null)
{
- return new AccessToken($userIdentifier, $scopes, $clientEntity);
+ return new Passport::$accessTokenEntity($userIdentifier, $scopes, $clientEntity);
}
/**
diff --git a/src/Bridge/PersonalAccessGrant.php b/src/Bridge/PersonalAccessGrant.php
index 289a0257a..4eb5f869c 100644
--- a/src/Bridge/PersonalAccessGrant.php
+++ b/src/Bridge/PersonalAccessGrant.php
@@ -20,14 +20,22 @@ public function respondToAccessTokenRequest(
// Validate request
$client = $this->validateClient($request);
$scopes = $this->validateScopes($this->getRequestParameter('scope', $request));
+ $userIdentifier = $this->getRequestParameter('user_id', $request);
// Finalize the requested scopes
- $scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client);
+ $scopes = $this->scopeRepository->finalizeScopes(
+ $scopes,
+ $this->getIdentifier(),
+ $client,
+ $userIdentifier
+ );
// Issue and persist access token
$accessToken = $this->issueAccessToken(
- $accessTokenTTL, $client,
- $this->getRequestParameter('user_id', $request), $scopes
+ $accessTokenTTL,
+ $client,
+ $userIdentifier,
+ $scopes
);
// Inject access token into response type
diff --git a/src/Bridge/ScopeRepository.php b/src/Bridge/ScopeRepository.php
index 4e7e7ec12..05e0ac9a1 100644
--- a/src/Bridge/ScopeRepository.php
+++ b/src/Bridge/ScopeRepository.php
@@ -2,12 +2,31 @@
namespace Laravel\Passport\Bridge;
+use Laravel\Passport\ClientRepository;
use Laravel\Passport\Passport;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
class ScopeRepository implements ScopeRepositoryInterface
{
+ /**
+ * The client repository.
+ *
+ * @var \Laravel\Passport\ClientRepository|null
+ */
+ protected ?ClientRepository $clients;
+
+ /**
+ * Create a new scope repository.
+ *
+ * @param \Laravel\Passport\ClientRepository|null $clients
+ * @return void
+ */
+ public function __construct(?ClientRepository $clients = null)
+ {
+ $this->clients = $clients;
+ }
+
/**
* {@inheritdoc}
*/
@@ -31,8 +50,12 @@ public function finalizeScopes(
})->values()->all();
}
+ $client = $this->clients?->findActive($clientEntity->getIdentifier());
+
return collect($scopes)->filter(function ($scope) {
return Passport::hasScope($scope->getIdentifier());
+ })->when($client, function ($scopes, $client) {
+ return $scopes->filter(fn ($scope) => $client->hasScope($scope->getIdentifier()));
})->values()->all();
}
}
diff --git a/src/Client.php b/src/Client.php
index f75fb7b97..9a8b9e202 100644
--- a/src/Client.php
+++ b/src/Client.php
@@ -10,6 +10,7 @@
class Client extends Model
{
use HasFactory;
+ use ResolvesInheritedScopes;
/**
* The database table used by the model.
@@ -41,8 +42,10 @@ class Client extends Model
*/
protected $casts = [
'grant_types' => 'array',
+ 'scopes' => 'array',
'personal_access_client' => 'bool',
'password_client' => 'bool',
+ 'device_client' => 'bool',
'revoked' => 'bool',
];
@@ -154,6 +157,31 @@ public function skipsAuthorization()
return false;
}
+ /**
+ * Determine whether the client has the given scope.
+ *
+ * @param string $scope
+ * @return bool
+ */
+ public function hasScope($scope)
+ {
+ if (! is_array($this->scopes)) {
+ return true;
+ }
+
+ $scopes = Passport::$withInheritedScopes
+ ? $this->resolveInheritedScopes($scope)
+ : [$scope];
+
+ foreach ($scopes as $scope) {
+ if (in_array($scope, $this->scopes)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* Determine if the client is a confidential client.
*
@@ -184,16 +212,6 @@ public function getIncrementing()
return Passport::clientUuids() ? false : $this->incrementing;
}
- /**
- * Get the current connection name for the model.
- *
- * @return string|null
- */
- public function getConnectionName()
- {
- return config('passport.storage.database.connection') ?? $this->connection;
- }
-
/**
* Create a new factory instance for the model.
*
diff --git a/src/ClientRepository.php b/src/ClientRepository.php
index f3e617e80..e2c5cc5e1 100644
--- a/src/ClientRepository.php
+++ b/src/ClientRepository.php
@@ -37,7 +37,7 @@ public function __construct($personalAccessClientId = null, $personalAccessClien
/**
* Get a client by the given ID.
*
- * @param int $id
+ * @param int|string $id
* @return \Laravel\Passport\Client|null
*/
public function find($id)
@@ -50,7 +50,7 @@ public function find($id)
/**
* Get an active client by the given ID.
*
- * @param int $id
+ * @param int|string $id
* @return \Laravel\Passport\Client|null
*/
public function findActive($id)
@@ -63,7 +63,7 @@ public function findActive($id)
/**
* Get a client instance for the given ID and user ID.
*
- * @param int $clientId
+ * @param int|string $clientId
* @param mixed $userId
* @return \Laravel\Passport\Client|null
*/
@@ -128,7 +128,7 @@ public function personalAccessClient()
/**
* Store a new client.
*
- * @param int $userId
+ * @param int|null $userId
* @param string $name
* @param string $redirect
* @param string|null $provider
@@ -161,7 +161,7 @@ public function create($userId, $name, $redirect, $provider = null, $personalAcc
/**
* Store a new personal access token client.
*
- * @param int $userId
+ * @param int|null $userId
* @param string $name
* @param string $redirect
* @return \Laravel\Passport\Client
@@ -170,7 +170,7 @@ public function createPersonalAccessClient($userId, $name, $redirect)
{
return tap($this->create($userId, $name, $redirect, null, true), function ($client) {
$accessClient = Passport::personalAccessClient();
- $accessClient->client_id = $client->id;
+ $accessClient->client_id = $client->getKey();
$accessClient->save();
});
}
@@ -178,10 +178,10 @@ public function createPersonalAccessClient($userId, $name, $redirect)
/**
* Store a new password grant client.
*
- * @param int $userId
+ * @param int|null $userId
* @param string $name
* @param string $redirect
- * @param string|null $provider
+ * @param string|null $provider
* @return \Laravel\Passport\Client
*/
public function createPasswordGrantClient($userId, $name, $redirect, $provider = null)
@@ -195,6 +195,7 @@ public function createPasswordGrantClient($userId, $name, $redirect, $provider =
* @param int $userId
* @param string $name
* @param string $redirect
+ * @param string|null $provider
* @return \Laravel\Passport\Client
*/
public function createDeviceCodeGrantClient($userId, $name, $redirect, $provider = null)
@@ -237,7 +238,7 @@ public function regenerateSecret(Client $client)
/**
* Determine if the given client is revoked.
*
- * @param int $id
+ * @param int|string $id
* @return bool
*/
public function revoked($id)
diff --git a/src/Console/ClientCommand.php b/src/Console/ClientCommand.php
index f92a4f846..d5172d3d8 100644
--- a/src/Console/ClientCommand.php
+++ b/src/Console/ClientCommand.php
@@ -104,6 +104,14 @@ protected function createPasswordClient(ClientRepository $clients)
in_array('users', $providers) ? 'users' : null
);
+ $providers = array_keys(config('auth.providers'));
+
+ $provider = $this->option('provider') ?: $this->choice(
+ 'Which user provider should this client use to retrieve users?',
+ $providers,
+ in_array('users', $providers) ? 'users' : null
+ );
+
$client = $clients->createPasswordGrantClient(
null, $name, 'http://localhost', $provider
);
@@ -144,7 +152,7 @@ protected function createClientCredentialsClient(ClientRepository $clients)
protected function createAuthCodeClient(ClientRepository $clients)
{
$userId = $this->option('user_id') ?: $this->ask(
- 'Which user ID should the client be assigned to?'
+ 'Which user ID should the client be assigned to? (Optional)'
);
$name = $this->option('name') ?: $this->ask(
@@ -208,7 +216,7 @@ protected function outputClientDetails(Client $client)
$this->line('');
}
- $this->line('Client ID: '.$client->id);
+ $this->line('Client ID: '.$client->getKey());
$this->line('Client secret: '.$client->plainSecret);
}
}
diff --git a/src/Console/PurgeCommand.php b/src/Console/PurgeCommand.php
index 7854c6ab2..9d9e66bcf 100644
--- a/src/Console/PurgeCommand.php
+++ b/src/Console/PurgeCommand.php
@@ -15,7 +15,8 @@ class PurgeCommand extends Command
*/
protected $signature = 'passport:purge
{--revoked : Only purge revoked tokens and authentication codes}
- {--expired : Only purge expired tokens and authentication codes}';
+ {--expired : Only purge expired tokens and authentication codes}
+ {--hours= : The number of hours to retain expired tokens}';
/**
* The console command description.
@@ -29,7 +30,9 @@ class PurgeCommand extends Command
*/
public function handle()
{
- $expired = Carbon::now()->subDays(7);
+ $expired = $this->option('hours')
+ ? Carbon::now()->subHours($this->option('hours'))
+ : Carbon::now()->subDays(7);
if (($this->option('revoked') && $this->option('expired')) ||
(! $this->option('revoked') && ! $this->option('expired'))) {
@@ -37,7 +40,9 @@ public function handle()
Passport::authCode()->where('revoked', 1)->orWhereDate('expires_at', '<', $expired)->delete();
Passport::refreshToken()->where('revoked', 1)->orWhereDate('expires_at', '<', $expired)->delete();
- $this->info('Purged revoked items and items expired for more than seven days.');
+ $this->option('hours')
+ ? $this->info('Purged revoked items and items expired for more than '.$this->option('hours').' hours.')
+ : $this->info('Purged revoked items and items expired for more than seven days.');
} elseif ($this->option('revoked')) {
Passport::token()->where('revoked', 1)->delete();
Passport::authCode()->where('revoked', 1)->delete();
@@ -49,7 +54,9 @@ public function handle()
Passport::authCode()->whereDate('expires_at', '<', $expired)->delete();
Passport::refreshToken()->whereDate('expires_at', '<', $expired)->delete();
- $this->info('Purged items expired for more than seven days.');
+ $this->option('hours')
+ ? $this->info('Purged items expired for more than '.$this->option('hours').' hours.')
+ : $this->info('Purged items expired for more than seven days.');
}
}
}
diff --git a/src/Contracts/AuthorizationViewResponse.php b/src/Contracts/AuthorizationViewResponse.php
new file mode 100644
index 000000000..6594c6624
--- /dev/null
+++ b/src/Contracts/AuthorizationViewResponse.php
@@ -0,0 +1,16 @@
+server = $server;
$this->tokens = $tokens;
$this->clients = $clients;
$this->provider = $provider;
$this->encrypter = $encrypter;
+ $this->request = $request;
}
/**
- * Determine if the requested provider matches the client's provider.
+ * Get the user for the incoming request.
*
- * @param \Illuminate\Http\Request $request
- * @return bool
+ * @return mixed
*/
- protected function hasValidProvider(Request $request)
+ public function user()
{
- $client = $this->client($request);
-
- if ($client && ! $client->provider) {
- return true;
+ if (! is_null($this->user)) {
+ return $this->user;
}
- return $client && $client->provider === $this->provider->getProviderName();
+ if ($this->request->bearerToken()) {
+ return $this->user = $this->authenticateViaBearerToken($this->request);
+ } elseif ($this->request->cookie(Passport::cookie())) {
+ return $this->user = $this->authenticateViaCookie($this->request);
+ }
}
/**
- * Get the user for the incoming request.
+ * Validate a user's credentials.
*
- * @param \Illuminate\Http\Request $request
- * @return mixed
+ * @param array $credentials
+ * @return bool
*/
- public function user(Request $request)
+ public function validate(array $credentials = [])
{
- if ($request->bearerToken()) {
- return $this->authenticateViaBearerToken($request);
- } elseif ($request->cookie(Passport::cookie())) {
- return $this->authenticateViaCookie($request);
- }
+ return ! is_null((new static(
+ $this->server,
+ $this->provider,
+ $this->tokens,
+ $this->clients,
+ $this->encrypter,
+ $credentials['request'],
+ ))->user());
}
/**
* Get the client for the incoming request.
*
- * @param \Illuminate\Http\Request $request
- * @return mixed
+ * @return \Laravel\Passport\Client|null
*/
- public function client(Request $request)
+ public function client()
{
- if ($request->bearerToken()) {
- if (! $psr = $this->getPsrRequestViaBearerToken($request)) {
+ if (! is_null($this->client)) {
+ return $this->client;
+ }
+
+ if ($this->request->bearerToken()) {
+ if (! $psr = $this->getPsrRequestViaBearerToken($this->request)) {
return;
}
- return $this->clients->findActive(
+ return $this->client = $this->clients->findActive(
$psr->getAttribute('oauth_client_id')
);
- } elseif ($request->cookie(Passport::cookie())) {
- if ($token = $this->getTokenViaCookie($request)) {
- return $this->clients->findActive($token['aud']);
+ } elseif ($this->request->cookie(Passport::cookie())) {
+ if ($token = $this->getTokenViaCookie($this->request)) {
+ return $this->client = $this->clients->findActive($token['aud']);
}
}
}
@@ -149,7 +179,13 @@ protected function authenticateViaBearerToken($request)
return;
}
- if (! $this->hasValidProvider($request)) {
+ $client = $this->clients->findActive(
+ $psr->getAttribute('oauth_client_id')
+ );
+
+ if (! $client ||
+ ($client->provider &&
+ $client->provider !== $this->provider->getProviderName())) {
return;
}
@@ -171,15 +207,6 @@ protected function authenticateViaBearerToken($request)
$psr->getAttribute('oauth_access_token_id')
);
- $clientId = $psr->getAttribute('oauth_client_id');
-
- // Finally, we will verify if the client that issued this token is still valid and
- // its tokens may still be used. If not, we will bail out since we don't want a
- // user to be able to send access tokens for deleted or revoked applications.
- if ($this->clients->revoked($clientId)) {
- return;
- }
-
return $token ? $user->withAccessToken($token) : null;
}
@@ -187,7 +214,7 @@ protected function authenticateViaBearerToken($request)
* Authenticate and get the incoming PSR-7 request via the Bearer token.
*
* @param \Illuminate\Http\Request $request
- * @return \Psr\Http\Message\ServerRequestInterface
+ * @return \Psr\Http\Message\ServerRequestInterface|null
*/
protected function getPsrRequestViaBearerToken($request)
{
@@ -268,8 +295,12 @@ protected function getTokenViaCookie($request)
*/
protected function decodeJwtTokenCookie($request)
{
+ $jwt = $request->cookie(Passport::cookie());
+
return (array) JWT::decode(
- CookieValuePrefix::remove($this->encrypter->decrypt($request->cookie(Passport::cookie()), Passport::$unserializesCookies)),
+ Passport::$decryptsCookies
+ ? CookieValuePrefix::remove($this->encrypter->decrypt($jwt, Passport::$unserializesCookies))
+ : $jwt,
new Key(Passport::tokenEncryptionKey($this->encrypter), 'HS256')
);
}
@@ -305,6 +336,19 @@ protected function getTokenFromRequest($request)
return $token;
}
+ /**
+ * Set the current request instance.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return $this
+ */
+ public function setRequest(Request $request)
+ {
+ $this->request = $request;
+
+ return $this;
+ }
+
/**
* Determine if the cookie contents should be serialized.
*
@@ -314,4 +358,17 @@ public static function serialized()
{
return EncryptCookies::serialized('XSRF-TOKEN');
}
+
+ /**
+ * Set the client for the current request.
+ *
+ * @param \Laravel\Passport\Client $client
+ * @return $this
+ */
+ public function setClient(Client $client)
+ {
+ $this->client = $client;
+
+ return $this;
+ }
}
diff --git a/src/HasApiTokens.php b/src/HasApiTokens.php
index 198575483..e1479cc2c 100644
--- a/src/HasApiTokens.php
+++ b/src/HasApiTokens.php
@@ -9,7 +9,7 @@ trait HasApiTokens
/**
* The current access token for the authentication user.
*
- * @var \Laravel\Passport\Token
+ * @var \Laravel\Passport\Token|\Laravel\Passport\TransientToken|null
*/
protected $accessToken;
@@ -36,7 +36,7 @@ public function tokens()
/**
* Get the current access token being used by the user.
*
- * @return \Laravel\Passport\Token|null
+ * @return \Laravel\Passport\Token|\Laravel\Passport\TransientToken|null
*/
public function token()
{
@@ -46,7 +46,7 @@ public function token()
/**
* Determine if the current API token has a given scope.
*
- * @param string $scope
+ * @param string $scope
* @return bool
*/
public function tokenCan($scope)
@@ -57,8 +57,8 @@ public function tokenCan($scope)
/**
* Create a new personal access token for the user.
*
- * @param string $name
- * @param array $scopes
+ * @param string $name
+ * @param array $scopes
* @return \Laravel\Passport\PersonalAccessTokenResult
*/
public function createToken($name, array $scopes = [])
@@ -71,7 +71,7 @@ public function createToken($name, array $scopes = [])
/**
* Set the current access token for the user.
*
- * @param \Laravel\Passport\Token $accessToken
+ * @param \Laravel\Passport\Token|\Laravel\Passport\TransientToken|null $accessToken
* @return $this
*/
public function withAccessToken($accessToken)
diff --git a/src/Http/Controllers/AccessTokenController.php b/src/Http/Controllers/AccessTokenController.php
index 3b77a8477..7c3b395bc 100644
--- a/src/Http/Controllers/AccessTokenController.php
+++ b/src/Http/Controllers/AccessTokenController.php
@@ -3,7 +3,6 @@
namespace Laravel\Passport\Http\Controllers;
use Laravel\Passport\TokenRepository;
-use Lcobucci\JWT\Parser as JwtParser;
use League\OAuth2\Server\AuthorizationServer;
use Nyholm\Psr7\Response as Psr7Response;
use Psr\Http\Message\ServerRequestInterface;
@@ -26,29 +25,16 @@ class AccessTokenController
*/
protected $tokens;
- /**
- * The JWT parser instance.
- *
- * @var \Lcobucci\JWT\Parser
- *
- * @deprecated This property will be removed in a future Passport version.
- */
- protected $jwt;
-
/**
* Create a new controller instance.
*
- * @param \League\OAuth2\Server\AuthorizationServer $server
- * @param \Laravel\Passport\TokenRepository $tokens
- * @param \Lcobucci\JWT\Parser $jwt
+ * @param \League\OAuth2\Server\AuthorizationServer $server
+ * @param \Laravel\Passport\TokenRepository $tokens
* @return void
*/
- public function __construct(
- AuthorizationServer $server,
- TokenRepository $tokens,
- JwtParser $jwt
- ) {
- $this->jwt = $jwt;
+ public function __construct(AuthorizationServer $server,
+ TokenRepository $tokens)
+ {
$this->server = $server;
$this->tokens = $tokens;
}
@@ -56,7 +42,7 @@ public function __construct(
/**
* Authorize a client to access the user's account.
*
- * @param \Psr\Http\Message\ServerRequestInterface $request
+ * @param \Psr\Http\Message\ServerRequestInterface $request
* @return \Illuminate\Http\Response
*/
public function issueToken(ServerRequestInterface $request)
diff --git a/src/Http/Controllers/ApproveAuthorizationController.php b/src/Http/Controllers/ApproveAuthorizationController.php
index 1afcdccbf..decd754c3 100644
--- a/src/Http/Controllers/ApproveAuthorizationController.php
+++ b/src/Http/Controllers/ApproveAuthorizationController.php
@@ -8,7 +8,7 @@
class ApproveAuthorizationController
{
- use ConvertsPsrResponses, RetrievesAuthRequestFromSession;
+ use ConvertsPsrResponses, HandlesOAuthErrors, RetrievesAuthRequestFromSession;
/**
* The authorization server.
@@ -40,8 +40,12 @@ public function approve(Request $request)
$authRequest = $this->getAuthRequestFromSession($request);
- return $this->convertResponse(
- $this->server->completeAuthorizationRequest($authRequest, new Psr7Response)
- );
+ $authRequest->setAuthorizationApproved(true);
+
+ return $this->withErrorHandling(function () use ($authRequest) {
+ return $this->convertResponse(
+ $this->server->completeAuthorizationRequest($authRequest, new Psr7Response)
+ );
+ });
}
}
diff --git a/src/Http/Controllers/AuthorizationController.php b/src/Http/Controllers/AuthorizationController.php
index 22e08ef23..38942232b 100644
--- a/src/Http/Controllers/AuthorizationController.php
+++ b/src/Http/Controllers/AuthorizationController.php
@@ -2,14 +2,17 @@
namespace Laravel\Passport\Http\Controllers;
-use Illuminate\Contracts\Routing\ResponseFactory;
+use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Laravel\Passport\Bridge\User;
use Laravel\Passport\ClientRepository;
+use Laravel\Passport\Contracts\AuthorizationViewResponse;
+use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\Passport;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\AuthorizationServer;
+use League\OAuth2\Server\Exception\OAuthServerException;
use Nyholm\Psr7\Response as Psr7Response;
use Psr\Http\Message\ServerRequestInterface;
@@ -25,9 +28,16 @@ class AuthorizationController
protected $server;
/**
- * The response factory implementation.
+ * The guard implementation.
*
- * @var \Illuminate\Contracts\Routing\ResponseFactory
+ * @var \Illuminate\Contracts\Auth\StatefulGuard
+ */
+ protected $guard;
+
+ /**
+ * The authorization view response implementation.
+ *
+ * @var \Laravel\Passport\Contracts\AuthorizationViewResponse
*/
protected $response;
@@ -35,12 +45,16 @@ class AuthorizationController
* Create a new controller instance.
*
* @param \League\OAuth2\Server\AuthorizationServer $server
- * @param \Illuminate\Contracts\Routing\ResponseFactory $response
+ * @param \Illuminate\Contracts\Auth\StatefulGuard $guard
+ * @param \Laravel\Passport\Contracts\AuthorizationViewResponse $response
* @return void
*/
- public function __construct(AuthorizationServer $server, ResponseFactory $response)
+ public function __construct(AuthorizationServer $server,
+ StatefulGuard $guard,
+ AuthorizationViewResponse $response)
{
$this->server = $server;
+ $this->guard = $guard;
$this->response = $response;
}
@@ -51,7 +65,7 @@ public function __construct(AuthorizationServer $server, ResponseFactory $respon
* @param \Illuminate\Http\Request $request
* @param \Laravel\Passport\ClientRepository $clients
* @param \Laravel\Passport\TokenRepository $tokens
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\Response|\Laravel\Passport\Contracts\AuthorizationViewResponse
*/
public function authorize(ServerRequestInterface $psrRequest,
Request $request,
@@ -62,22 +76,40 @@ public function authorize(ServerRequestInterface $psrRequest,
return $this->server->validateAuthorizationRequest($psrRequest);
});
- $scopes = $this->parseScopes($authRequest);
+ if ($this->guard->guest()) {
+ return $request->get('prompt') === 'none'
+ ? $this->denyRequest($authRequest)
+ : $this->promptForLogin($request);
+ }
- $token = $tokens->findValidToken(
- $user = $request->user(),
- $client = $clients->find($authRequest->getClient()->getIdentifier())
- );
+ if ($request->get('prompt') === 'login' &&
+ ! $request->session()->get('promptedForLogin', false)) {
+ $this->guard->logout();
+ $request->session()->invalidate();
+ $request->session()->regenerateToken();
+
+ return $this->promptForLogin($request);
+ }
+
+ $request->session()->forget('promptedForLogin');
- if (($token && $token->scopes === collect($scopes)->pluck('id')->all()) ||
- $client->skipsAuthorization()) {
+ $scopes = $this->parseScopes($authRequest);
+ $user = $this->guard->user();
+ $client = $clients->find($authRequest->getClient()->getIdentifier());
+
+ if ($request->get('prompt') !== 'consent' &&
+ ($client->skipsAuthorization() || $this->hasValidToken($tokens, $user, $client, $scopes))) {
return $this->approveRequest($authRequest, $user);
}
+ if ($request->get('prompt') === 'none') {
+ return $this->denyRequest($authRequest, $user);
+ }
+
$request->session()->put('authToken', $authToken = Str::random());
$request->session()->put('authRequest', $authRequest);
- return $this->response->view('passport::authorize', [
+ return $this->response->withParameters([
'client' => $client,
'user' => $user,
'scopes' => $scopes,
@@ -101,11 +133,27 @@ protected function parseScopes($authRequest)
);
}
+ /**
+ * Determine if a valid token exists for the given user, client, and scopes.
+ *
+ * @param \Laravel\Passport\TokenRepository $tokens
+ * @param \Illuminate\Contracts\Auth\Authenticatable $user
+ * @param \Laravel\Passport\Client $client
+ * @param array $scopes
+ * @return bool
+ */
+ protected function hasValidToken($tokens, $user, $client, $scopes)
+ {
+ $token = $tokens->findValidToken($user, $client);
+
+ return $token && $token->scopes === collect($scopes)->pluck('id')->all();
+ }
+
/**
* Approve the authorization request.
*
* @param \League\OAuth2\Server\RequestTypes\AuthorizationRequest $authRequest
- * @param \Illuminate\Database\Eloquent\Model $user
+ * @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return \Illuminate\Http\Response
*/
protected function approveRequest($authRequest, $user)
@@ -120,4 +168,53 @@ protected function approveRequest($authRequest, $user)
);
});
}
+
+ /**
+ * Deny the authorization request.
+ *
+ * @param \League\OAuth2\Server\RequestTypes\AuthorizationRequest $authRequest
+ * @param \Illuminate\Contracts\Auth\Authenticatable|null $user
+ * @return \Illuminate\Http\Response
+ */
+ protected function denyRequest($authRequest, $user = null)
+ {
+ if (is_null($user)) {
+ $uri = $authRequest->getRedirectUri()
+ ?? (is_array($authRequest->getClient()->getRedirectUri())
+ ? $authRequest->getClient()->getRedirectUri()[0]
+ : $authRequest->getClient()->getRedirectUri());
+
+ $separator = $authRequest->getGrantTypeId() === 'implicit' ? '#' : '?';
+
+ $uri = $uri.(str_contains($uri, $separator) ? '&' : $separator).'state='.$authRequest->getState();
+
+ return $this->withErrorHandling(function () use ($uri) {
+ throw OAuthServerException::accessDenied('Unauthenticated', $uri);
+ });
+ }
+
+ $authRequest->setUser(new User($user->getAuthIdentifier()));
+
+ $authRequest->setAuthorizationApproved(false);
+
+ return $this->withErrorHandling(function () use ($authRequest) {
+ return $this->convertResponse(
+ $this->server->completeAuthorizationRequest($authRequest, new Psr7Response)
+ );
+ });
+ }
+
+ /**
+ * Prompt the user to login by throwing an AuthenticationException.
+ *
+ * @param \Illuminate\Http\Request $request
+ *
+ * @throws \Laravel\Passport\Exceptions\AuthenticationException
+ */
+ protected function promptForLogin($request)
+ {
+ $request->session()->put('promptedForLogin', true);
+
+ throw new AuthenticationException;
+ }
}
diff --git a/src/Http/Controllers/DenyAuthorizationController.php b/src/Http/Controllers/DenyAuthorizationController.php
index 49411e3ca..3a46e6174 100644
--- a/src/Http/Controllers/DenyAuthorizationController.php
+++ b/src/Http/Controllers/DenyAuthorizationController.php
@@ -2,36 +2,36 @@
namespace Laravel\Passport\Http\Controllers;
-use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Http\Request;
-use Illuminate\Support\Arr;
+use League\OAuth2\Server\AuthorizationServer;
+use Nyholm\Psr7\Response as Psr7Response;
class DenyAuthorizationController
{
- use RetrievesAuthRequestFromSession;
+ use ConvertsPsrResponses, HandlesOAuthErrors, RetrievesAuthRequestFromSession;
/**
- * The response factory implementation.
+ * The authorization server.
*
- * @var \Illuminate\Contracts\Routing\ResponseFactory
+ * @var \League\OAuth2\Server\AuthorizationServer
*/
- protected $response;
+ protected $server;
/**
* Create a new controller instance.
*
- * @param \Illuminate\Contracts\Routing\ResponseFactory $response
+ * @param \League\OAuth2\Server\AuthorizationServer $server
* @return void
*/
- public function __construct(ResponseFactory $response)
+ public function __construct(AuthorizationServer $server)
{
- $this->response = $response;
+ $this->server = $server;
}
/**
* Deny the authorization request.
*
- * @param \Illuminate\Http\Request $request
+ * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function deny(Request $request)
@@ -40,16 +40,12 @@ public function deny(Request $request)
$authRequest = $this->getAuthRequestFromSession($request);
- $clientUris = Arr::wrap($authRequest->getClient()->getRedirectUri());
+ $authRequest->setAuthorizationApproved(false);
- if (!in_array($uri = $authRequest->getRedirectUri(), $clientUris)) {
- $uri = Arr::first($clientUris);
- }
-
- $separator = $authRequest->getGrantTypeId() === 'implicit' ? '#' : (strstr($uri, '?') ? '&' : '?');
-
- return $this->response->redirectTo(
- $uri . $separator . 'error=access_denied&state=' . $request->input('state')
- );
+ return $this->withErrorHandling(function () use ($authRequest) {
+ return $this->convertResponse(
+ $this->server->completeAuthorizationRequest($authRequest, new Psr7Response)
+ );
+ });
}
}
diff --git a/src/Http/Controllers/RetrievesAuthRequestFromSession.php b/src/Http/Controllers/RetrievesAuthRequestFromSession.php
index b1296277c..0a23e1ec4 100644
--- a/src/Http/Controllers/RetrievesAuthRequestFromSession.php
+++ b/src/Http/Controllers/RetrievesAuthRequestFromSession.php
@@ -42,8 +42,6 @@ protected function getAuthRequestFromSession(Request $request)
}
$authRequest->setUser(new User($request->user()->getAuthIdentifier()));
-
- $authRequest->setAuthorizationApproved(true);
});
}
}
diff --git a/src/Http/Middleware/CheckClientCredentials.php b/src/Http/Middleware/CheckClientCredentials.php
index fc6e049e6..25644dd24 100644
--- a/src/Http/Middleware/CheckClientCredentials.php
+++ b/src/Http/Middleware/CheckClientCredentials.php
@@ -2,7 +2,7 @@
namespace Laravel\Passport\Http\Middleware;
-use Illuminate\Auth\AuthenticationException;
+use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\Exceptions\MissingScopeException;
class CheckClientCredentials extends CheckCredentials
@@ -13,7 +13,7 @@ class CheckClientCredentials extends CheckCredentials
* @param \Laravel\Passport\Token $token
* @return void
*
- * @throws \Illuminate\Auth\AuthenticationException
+ * @throws \Laravel\Passport\Exceptions\AuthenticationException
*/
protected function validateCredentials($token)
{
diff --git a/src/Http/Middleware/CheckClientCredentialsForAnyScope.php b/src/Http/Middleware/CheckClientCredentialsForAnyScope.php
index 7c63de92a..8da32bf19 100644
--- a/src/Http/Middleware/CheckClientCredentialsForAnyScope.php
+++ b/src/Http/Middleware/CheckClientCredentialsForAnyScope.php
@@ -2,7 +2,7 @@
namespace Laravel\Passport\Http\Middleware;
-use Illuminate\Auth\AuthenticationException;
+use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\Exceptions\MissingScopeException;
class CheckClientCredentialsForAnyScope extends CheckCredentials
@@ -13,7 +13,7 @@ class CheckClientCredentialsForAnyScope extends CheckCredentials
* @param \Laravel\Passport\Token $token
* @return void
*
- * @throws \Illuminate\Auth\AuthenticationException
+ * @throws \Laravel\Passport\Exceptions\AuthenticationException
*/
protected function validateCredentials($token)
{
diff --git a/src/Http/Middleware/CheckCredentials.php b/src/Http/Middleware/CheckCredentials.php
index 20a937632..5d7d9273f 100644
--- a/src/Http/Middleware/CheckCredentials.php
+++ b/src/Http/Middleware/CheckCredentials.php
@@ -3,7 +3,7 @@
namespace Laravel\Passport\Http\Middleware;
use Closure;
-use Illuminate\Auth\AuthenticationException;
+use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\ResourceServer;
@@ -39,6 +39,21 @@ public function __construct(ResourceServer $server, TokenRepository $repository)
$this->repository = $repository;
}
+ /**
+ * Specify the scopes for the middleware.
+ *
+ * @param array|string $scopes
+ * @return string
+ */
+ public static function using(...$scopes)
+ {
+ if (is_array($scopes[0])) {
+ return static::class.':'.implode(',', $scopes[0]);
+ }
+
+ return static::class.':'.implode(',', $scopes);
+ }
+
/**
* Handle an incoming request.
*
@@ -47,7 +62,7 @@ public function __construct(ResourceServer $server, TokenRepository $repository)
* @param mixed ...$scopes
* @return mixed
*
- * @throws \Illuminate\Auth\AuthenticationException
+ * @throws \Laravel\Passport\Exceptions\AuthenticationException
*/
public function handle($request, Closure $next, ...$scopes)
{
@@ -93,7 +108,7 @@ protected function validate($psr, $scopes)
* @param \Laravel\Passport\Token $token
* @return void
*
- * @throws \Illuminate\Auth\AuthenticationException
+ * @throws \Laravel\Passport\Exceptions\AuthenticationException
*/
abstract protected function validateCredentials($token);
diff --git a/src/Http/Middleware/CheckForAnyScope.php b/src/Http/Middleware/CheckForAnyScope.php
index 0bb9653f7..77d9bb1a3 100644
--- a/src/Http/Middleware/CheckForAnyScope.php
+++ b/src/Http/Middleware/CheckForAnyScope.php
@@ -2,11 +2,26 @@
namespace Laravel\Passport\Http\Middleware;
-use Illuminate\Auth\AuthenticationException;
+use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\Exceptions\MissingScopeException;
class CheckForAnyScope
{
+ /**
+ * Specify the scopes for the middleware.
+ *
+ * @param array|string $scopes
+ * @return string
+ */
+ public static function using(...$scopes)
+ {
+ if (is_array($scopes[0])) {
+ return static::class.':'.implode(',', $scopes[0]);
+ }
+
+ return static::class.':'.implode(',', $scopes);
+ }
+
/**
* Handle the incoming request.
*
@@ -15,7 +30,7 @@ class CheckForAnyScope
* @param mixed ...$scopes
* @return \Illuminate\Http\Response
*
- * @throws \Illuminate\Auth\AuthenticationException|\Laravel\Passport\Exceptions\MissingScopeException
+ * @throws \Laravel\Passport\Exceptions\AuthenticationException|\Laravel\Passport\Exceptions\MissingScopeException
*/
public function handle($request, $next, ...$scopes)
{
diff --git a/src/Http/Middleware/CheckScopes.php b/src/Http/Middleware/CheckScopes.php
index e19f1097e..fdcead33a 100644
--- a/src/Http/Middleware/CheckScopes.php
+++ b/src/Http/Middleware/CheckScopes.php
@@ -2,11 +2,26 @@
namespace Laravel\Passport\Http\Middleware;
-use Illuminate\Auth\AuthenticationException;
+use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\Exceptions\MissingScopeException;
class CheckScopes
{
+ /**
+ * Specify the scopes for the middleware.
+ *
+ * @param array|string $scopes
+ * @return string
+ */
+ public static function using(...$scopes)
+ {
+ if (is_array($scopes[0])) {
+ return static::class.':'.implode(',', $scopes[0]);
+ }
+
+ return static::class.':'.implode(',', $scopes);
+ }
+
/**
* Handle the incoming request.
*
@@ -15,7 +30,7 @@ class CheckScopes
* @param mixed ...$scopes
* @return \Illuminate\Http\Response
*
- * @throws \Illuminate\Auth\AuthenticationException|\Laravel\Passport\Exceptions\MissingScopeException
+ * @throws \Laravel\Passport\Exceptions\AuthenticationException|\Laravel\Passport\Exceptions\MissingScopeException
*/
public function handle($request, $next, ...$scopes)
{
diff --git a/src/Http/Middleware/CreateFreshApiToken.php b/src/Http/Middleware/CreateFreshApiToken.php
index 869da186e..0e93f6d11 100644
--- a/src/Http/Middleware/CreateFreshApiToken.php
+++ b/src/Http/Middleware/CreateFreshApiToken.php
@@ -35,6 +35,19 @@ public function __construct(ApiTokenCookieFactory $cookieFactory)
$this->cookieFactory = $cookieFactory;
}
+ /**
+ * Specify the guard for the middleware.
+ *
+ * @param string|null $guard
+ * @return string
+ */
+ public static function using($guard = null)
+ {
+ $guard = is_null($guard) ? '' : ':'.$guard;
+
+ return static::class.$guard;
+ }
+
/**
* Handle an incoming request.
*
diff --git a/src/Http/Responses/AuthorizationViewResponse.php b/src/Http/Responses/AuthorizationViewResponse.php
new file mode 100644
index 000000000..36761d486
--- /dev/null
+++ b/src/Http/Responses/AuthorizationViewResponse.php
@@ -0,0 +1,68 @@
+view = $view;
+ }
+
+ /**
+ * Add parameters to response.
+ *
+ * @param array $parameters
+ * @return $this
+ */
+ public function withParameters($parameters = [])
+ {
+ $this->parameters = $parameters;
+
+ return $this;
+ }
+
+ /**
+ * Create an HTTP response that represents the object.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Symfony\Component\HttpFoundation\Response
+ */
+ public function toResponse($request)
+ {
+ if (! is_callable($this->view) || is_string($this->view)) {
+ return response()->view($this->view, $this->parameters);
+ }
+
+ $response = call_user_func($this->view, $this->parameters);
+
+ if ($response instanceof Responsable) {
+ return $response->toResponse($request);
+ }
+
+ return $response;
+ }
+}
diff --git a/src/Passport.php b/src/Passport.php
index 506377309..ae38c38ae 100644
--- a/src/Passport.php
+++ b/src/Passport.php
@@ -6,7 +6,8 @@
use DateInterval;
use DateTimeInterface;
use Illuminate\Contracts\Encryption\Encrypter;
-use Illuminate\Support\Facades\Route;
+use Laravel\Passport\Contracts\AuthorizationViewResponse as AuthorizationViewResponseContract;
+use Laravel\Passport\Http\Responses\AuthorizationViewResponse;
use League\OAuth2\Server\ResourceServer;
use Mockery;
use Psr\Http\Message\ServerRequestInterface;
@@ -36,15 +37,6 @@ class Passport
//
];
- /**
- * The date when access tokens expire.
- *
- * @var \DateTimeInterface|null
- *
- * @deprecated Will be removed in the next major Passport release.
- */
- public static $tokensExpireAt;
-
/**
* The interval when access tokens expire.
*
@@ -52,15 +44,6 @@ class Passport
*/
public static $tokensExpireIn;
- /**
- * The date when refresh tokens expire.
- *
- * @var \DateTimeInterface|null
- *
- * @deprecated Will be removed in the next major Passport release.
- */
- public static $refreshTokensExpireAt;
-
/**
* The date when refresh tokens expire.
*
@@ -68,15 +51,6 @@ class Passport
*/
public static $refreshTokensExpireIn;
- /**
- * The date when personal access tokens expire.
- *
- * @var \DateTimeInterface|null
- *
- * @deprecated Will be removed in the next major Passport release.
- */
- public static $personalAccessTokensExpireAt;
-
/**
* The date when personal access tokens expire.
*
@@ -105,6 +79,13 @@ class Passport
*/
public static $keyPath;
+ /**
+ * The access token entity class name.
+ *
+ * @var string
+ */
+ public static $accessTokenEntity = 'Laravel\Passport\Bridge\AccessToken';
+
/**
* The auth code model class name.
*
@@ -171,6 +152,13 @@ class Passport
*/
public static $unserializesCookies = false;
+ /**
+ * Indicates if Passport should decrypt cookies.
+ *
+ * @var bool
+ */
+ public static $decryptsCookies = true;
+
/**
* Indicates if client secrets will be hashed.
*
@@ -199,6 +187,13 @@ class Passport
*/
public static $authorizationServerResponseType;
+ /**
+ * Indicates if Passport routes will be registered.
+ *
+ * @var bool
+ */
+ public static $registersRoutes = true;
+
/**
* Enable the implicit grant type.
*
@@ -211,31 +206,6 @@ public static function enableImplicitGrant()
return new static;
}
- /**
- * Binds the Passport routes into the controller.
- *
- * @param callable|null $callback
- * @param array $options
- * @return void
- */
- public static function routes($callback = null, array $options = [])
- {
- $callback = $callback ?: function ($router) {
- $router->all();
- };
-
- $defaultOptions = [
- 'prefix' => 'oauth',
- 'namespace' => '\Laravel\Passport\Http\Controllers',
- ];
-
- $options = array_merge($defaultOptions, $options);
-
- Route::group($options, function ($router) use ($callback) {
- $callback(new RouteRegistrar($router));
- });
- }
-
/**
* Set the default scope(s). Multiple scopes may be an array or specified delimited by spaces.
*
@@ -318,7 +288,6 @@ public static function tokensExpireIn(DateTimeInterface $date = null)
return static::$tokensExpireIn ?? new DateInterval('P1Y');
}
- static::$tokensExpireAt = $date;
static::$tokensExpireIn = Carbon::now()->diff($date);
return new static;
@@ -336,7 +305,6 @@ public static function refreshTokensExpireIn(DateTimeInterface $date = null)
return static::$refreshTokensExpireIn ?? new DateInterval('P1Y');
}
- static::$refreshTokensExpireAt = $date;
static::$refreshTokensExpireIn = Carbon::now()->diff($date);
return new static;
@@ -354,7 +322,6 @@ public static function personalAccessTokensExpireIn(DateTimeInterface $date = nu
return static::$personalAccessTokensExpireIn ?? new DateInterval('P1Y');
}
- static::$personalAccessTokensExpireAt = $date;
static::$personalAccessTokensExpireIn = Carbon::now()->diff($date);
return new static;
@@ -400,11 +367,9 @@ public static function ignoreCsrfToken($ignoreCsrfToken = true)
*/
public static function actingAs($user, $scopes = [], $guard = 'api')
{
- $token = Mockery::mock(self::tokenModel())->shouldIgnoreMissing(false);
+ $token = app(self::tokenModel());
- foreach ($scopes as $scope) {
- $token->shouldReceive('can')->with($scope)->andReturn(true);
- }
+ $token->scopes = $scopes;
$user->withAccessToken($token);
@@ -424,13 +389,14 @@ public static function actingAs($user, $scopes = [], $guard = 'api')
*
* @param \Laravel\Passport\Client $client
* @param array $scopes
+ * @param string $guard
* @return \Laravel\Passport\Client
*/
- public static function actingAsClient($client, $scopes = [])
+ public static function actingAsClient($client, $scopes = [], $guard = 'api')
{
$token = app(self::tokenModel());
- $token->client_id = $client->id;
+ $token->client_id = $client->getKey();
$token->setRelation('client', $client);
$token->scopes = $scopes;
@@ -450,6 +416,10 @@ public static function actingAsClient($client, $scopes = [])
app()->instance(TokenRepository::class, $mock);
+ app('auth')->guard($guard)->setClient($client);
+
+ app('auth')->shouldUse($guard);
+
return $client;
}
@@ -479,6 +449,17 @@ public static function keyPath($file)
: storage_path($file);
}
+ /**
+ * Set the access token entity class name.
+ *
+ * @param string $accessTokenEntity
+ * @return void
+ */
+ public static function useAccessTokenEntity($accessTokenEntity)
+ {
+ static::$accessTokenEntity = $accessTokenEntity;
+ }
+
/**
* Set the auth code model class name.
*
@@ -694,34 +675,28 @@ public static function tokenEncryptionKey(Encrypter $encrypter)
}
/**
- * Set the device code model class name.
+ * Specify which view should be used as the authorization view.
*
- * @param string $deviceCodeModel
+ * @param callable|string $view
* @return void
*/
- public static function useDeviceCodeModel($deviceCodeModel)
+ public static function authorizationView($view)
{
- static::$deviceCodeModel = $deviceCodeModel;
+ app()->singleton(AuthorizationViewResponseContract::class, function ($app) use ($view) {
+ return new AuthorizationViewResponse($view);
+ });
}
/**
- * Get the device code model class name.
+ * Configure Passport to not register its routes.
*
- * @return string
+ * @return static
*/
- public static function deviceCodeModel()
+ public static function ignoreRoutes()
{
- return static::$deviceCodeModel;
- }
+ static::$registersRoutes = false;
- /**
- * Get a new device code model instance.
- *
- * @return \Laravel\Passport\DeviceCode
- */
- public static function deviceCode()
- {
- return new static::$deviceCodeModel;
+ return new static;
}
/**
@@ -759,4 +734,60 @@ public static function withoutCookieSerialization()
return new static;
}
+
+ /**
+ * Instruct Passport to enable cookie encryption.
+ *
+ * @return static
+ */
+ public static function withCookieEncryption()
+ {
+ static::$decryptsCookies = true;
+
+ return new static;
+ }
+
+ /**
+ * Instruct Passport to disable cookie encryption.
+ *
+ * @return static
+ */
+ public static function withoutCookieEncryption()
+ {
+ static::$decryptsCookies = false;
+
+ return new static;
+ }
+
+ /**
+ *
+ * Set the device code model class name.
+ *
+ * @param string $deviceCodeModel
+ * @return void
+ */
+ public static function useDeviceCodeModel($deviceCodeModel)
+ {
+ static::$deviceCodeModel = $deviceCodeModel;
+ }
+
+ /**
+ * Get the device code model class name.
+ *
+ * @return string
+ */
+ public static function deviceCodeModel()
+ {
+ return static::$deviceCodeModel;
+ }
+
+ /**
+ * Get a new device code model instance.
+ *
+ * @return \Laravel\Passport\DeviceCode
+ */
+ public static function deviceCode()
+ {
+ return new static::$deviceCodeModel;
+ }
}
diff --git a/src/PassportServiceProvider.php b/src/PassportServiceProvider.php
index e87b51846..99e345e17 100644
--- a/src/PassportServiceProvider.php
+++ b/src/PassportServiceProvider.php
@@ -4,18 +4,21 @@
use DateInterval;
use Illuminate\Auth\Events\Logout;
-use Illuminate\Auth\RequestGuard;
use Illuminate\Config\Repository as Config;
+use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Request;
+use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Laravel\Passport\Bridge\PersonalAccessGrant;
use Laravel\Passport\Bridge\RefreshTokenRepository;
use Laravel\Passport\Guards\TokenGuard;
-use Lcobucci\JWT\Configuration;
-use Lcobucci\JWT\Parser;
+use Laravel\Passport\Http\Controllers\AuthorizationController;
+use Lcobucci\JWT\Encoding\JoseEncoder;
+use Lcobucci\JWT\Parser as ParserContract;
+use Lcobucci\JWT\Token\Parser;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Grant\AuthCodeGrant;
@@ -34,13 +37,63 @@ class PassportServiceProvider extends ServiceProvider
*/
public function boot()
{
- $this->loadViewsFrom(__DIR__.'/../resources/views', 'passport');
+ $this->registerRoutes();
+ $this->registerResources();
+ $this->registerMigrations();
+ $this->registerPublishing();
+ $this->registerCommands();
$this->deleteCookieOnLogout();
+ }
- if ($this->app->runningInConsole()) {
- $this->registerMigrations();
+ /**
+ * Register the Passport routes.
+ *
+ * @return void
+ */
+ protected function registerRoutes()
+ {
+ if (Passport::$registersRoutes) {
+ Route::group([
+ 'as' => 'passport.',
+ 'prefix' => config('passport.path', 'oauth'),
+ 'namespace' => 'Laravel\Passport\Http\Controllers',
+ ], function () {
+ $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
+ });
+ }
+ }
+
+ /**
+ * Register the Passport resources.
+ *
+ * @return void
+ */
+ protected function registerResources()
+ {
+ $this->loadViewsFrom(__DIR__.'/../resources/views', 'passport');
+ }
+
+ /**
+ * Register the Passport migration files.
+ *
+ * @return void
+ */
+ protected function registerMigrations()
+ {
+ if ($this->app->runningInConsole() && Passport::$runsMigrations && ! config('passport.client_uuids')) {
+ $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
+ }
+ }
+ /**
+ * Register the package's publishable resources.
+ *
+ * @return void
+ */
+ protected function registerPublishing()
+ {
+ if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__.'/../database/migrations' => database_path('migrations'),
], 'passport-migrations');
@@ -52,26 +105,24 @@ public function boot()
$this->publishes([
__DIR__.'/../config/passport.php' => config_path('passport.php'),
], 'passport-config');
-
- $this->commands([
- Console\InstallCommand::class,
- Console\ClientCommand::class,
- Console\HashCommand::class,
- Console\KeysCommand::class,
- Console\PurgeCommand::class,
- ]);
}
}
/**
- * Register Passport's migration files.
+ * Register the Passport Artisan commands.
*
* @return void
*/
- protected function registerMigrations()
+ protected function registerCommands()
{
- if (Passport::$runsMigrations && ! config('passport.client_uuids')) {
- $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
+ if ($this->app->runningInConsole()) {
+ $this->commands([
+ Console\InstallCommand::class,
+ Console\ClientCommand::class,
+ Console\HashCommand::class,
+ Console\KeysCommand::class,
+ Console\PurgeCommand::class,
+ ]);
}
}
@@ -86,11 +137,17 @@ public function register()
Passport::setClientUuids($this->app->make(Config::class)->get('passport.client_uuids', false));
+ $this->app->when(AuthorizationController::class)
+ ->needs(StatefulGuard::class)
+ ->give(fn () => Auth::guard(config('passport.guard', null)));
+
$this->registerAuthorizationServer();
$this->registerClientRepository();
$this->registerJWTParser();
$this->registerResourceServer();
$this->registerGuard();
+
+ Passport::authorizationView('passport::authorize');
}
/**
@@ -238,8 +295,8 @@ protected function registerClientRepository()
*/
protected function registerJWTParser()
{
- $this->app->singleton(Parser::class, function () {
- return Configuration::forUnsecuredSigner()->parser();
+ $this->app->singleton(ParserContract::class, function () {
+ return new Parser(new JoseEncoder);
});
}
@@ -295,19 +352,18 @@ protected function registerGuard()
* Make an instance of the token guard.
*
* @param array $config
- * @return \Illuminate\Auth\RequestGuard
+ * @return \Laravel\Passport\Guards\TokenGuard
*/
protected function makeGuard(array $config)
{
- return new RequestGuard(function ($request) use ($config) {
- return (new TokenGuard(
- $this->app->make(ResourceServer::class),
- new PassportUserProvider(Auth::createUserProvider($config['provider']), $config['provider']),
- $this->app->make(TokenRepository::class),
- $this->app->make(ClientRepository::class),
- $this->app->make('encrypter')
- ))->user($request);
- }, $this->app['request']);
+ return new TokenGuard(
+ $this->app->make(ResourceServer::class),
+ new PassportUserProvider(Auth::createUserProvider($config['provider']), $config['provider']),
+ $this->app->make(TokenRepository::class),
+ $this->app->make(ClientRepository::class),
+ $this->app->make('encrypter'),
+ $this->app->make('request')
+ );
}
/**
diff --git a/src/PersonalAccessTokenFactory.php b/src/PersonalAccessTokenFactory.php
index b849d069f..15ac840ad 100644
--- a/src/PersonalAccessTokenFactory.php
+++ b/src/PersonalAccessTokenFactory.php
@@ -35,8 +35,6 @@ class PersonalAccessTokenFactory
* The JWT token parser instance.
*
* @var \Lcobucci\JWT\Parser
- *
- * @deprecated This property will be removed in a future Passport version.
*/
protected $jwt;
@@ -100,7 +98,7 @@ protected function createRequest($client, $userId, array $scopes)
return (new ServerRequest('POST', 'not-important'))->withParsedBody([
'grant_type' => 'personal_access',
- 'client_id' => $client->id,
+ 'client_id' => $client->getKey(),
'client_secret' => $secret,
'user_id' => $userId,
'scope' => implode(' ', $scopes),
@@ -126,7 +124,7 @@ protected function dispatchRequestToAuthorizationServer(ServerRequestInterface $
* @param array $response
* @return \Laravel\Passport\Token
*/
- protected function findAccessToken(array $response)
+ public function findAccessToken(array $response)
{
return $this->tokens->find(
$this->jwt->parse($response['access_token'])->claims()->get('jti')
diff --git a/src/RefreshToken.php b/src/RefreshToken.php
index 376a97b82..f0f3265a7 100644
--- a/src/RefreshToken.php
+++ b/src/RefreshToken.php
@@ -41,15 +41,7 @@ class RefreshToken extends Model
*/
protected $casts = [
'revoked' => 'bool',
- ];
-
- /**
- * The attributes that should be mutated to dates.
- *
- * @var array
- */
- protected $dates = [
- 'expires_at',
+ 'expires_at' => 'datetime',
];
/**
diff --git a/src/RefreshTokenRepository.php b/src/RefreshTokenRepository.php
index ded3b0a85..bca0269e3 100644
--- a/src/RefreshTokenRepository.php
+++ b/src/RefreshTokenRepository.php
@@ -52,11 +52,11 @@ public function revokeRefreshToken($id)
* Revokes refresh tokens by access token id.
*
* @param string $tokenId
- * @return void
+ * @return mixed
*/
public function revokeRefreshTokensByAccessTokenId($tokenId)
{
- Passport::refreshToken()->where('access_token_id', $tokenId)->update(['revoked' => true]);
+ return Passport::refreshToken()->where('access_token_id', $tokenId)->update(['revoked' => true]);
}
/**
diff --git a/src/ResolvesInheritedScopes.php b/src/ResolvesInheritedScopes.php
new file mode 100644
index 000000000..e7658a165
--- /dev/null
+++ b/src/ResolvesInheritedScopes.php
@@ -0,0 +1,27 @@
+router = $router;
- }
-
- /**
- * Register routes for transient tokens, clients, and personal access tokens.
- *
- * @return void
- */
- public function all()
- {
- $this->forAuthorization();
- $this->forAccessTokens();
- $this->forTransientTokens();
- $this->forClients();
- $this->forPersonalAccessTokens();
- $this->forDeviceAuthorization();
- }
-
- /**
- * Register the routes needed for authorization.
- *
- * @return void
- */
- public function forAuthorization()
- {
- $this->router->group(['middleware' => ['web', 'auth']], function ($router) {
- $router->get('/authorize', [
- 'uses' => 'AuthorizationController@authorize',
- 'as' => 'passport.authorizations.authorize',
- ]);
-
- $router->post('/authorize', [
- 'uses' => 'ApproveAuthorizationController@approve',
- 'as' => 'passport.authorizations.approve',
- ]);
-
- $router->delete('/authorize', [
- 'uses' => 'DenyAuthorizationController@deny',
- 'as' => 'passport.authorizations.deny',
- ]);
- });
- }
-
- /**
- * Register the routes for retrieving and issuing access tokens.
- *
- * @return void
- */
- public function forAccessTokens()
- {
- $this->router->post('/token', [
- 'uses' => 'AccessTokenController@issueToken',
- 'as' => 'passport.token',
- 'middleware' => 'throttle',
- ]);
-
- $this->router->group(['middleware' => ['web', 'auth']], function ($router) {
- $router->get('/tokens', [
- 'uses' => 'AuthorizedAccessTokenController@forUser',
- 'as' => 'passport.tokens.index',
- ]);
-
- $router->delete('/tokens/{token_id}', [
- 'uses' => 'AuthorizedAccessTokenController@destroy',
- 'as' => 'passport.tokens.destroy',
- ]);
- });
- }
-
- /**
- * Register the routes needed for refreshing transient tokens.
- *
- * @return void
- */
- public function forTransientTokens()
- {
- $this->router->post('/token/refresh', [
- 'middleware' => ['web', 'auth'],
- 'uses' => 'TransientTokenController@refresh',
- 'as' => 'passport.token.refresh',
- ]);
- }
-
- /**
- * Register the routes needed for managing clients.
- *
- * @return void
- */
- public function forClients()
- {
- $this->router->group(['middleware' => ['web', 'auth']], function ($router) {
- $router->get('/clients', [
- 'uses' => 'ClientController@forUser',
- 'as' => 'passport.clients.index',
- ]);
-
- $router->post('/clients', [
- 'uses' => 'ClientController@store',
- 'as' => 'passport.clients.store',
- ]);
-
- $router->put('/clients/{client_id}', [
- 'uses' => 'ClientController@update',
- 'as' => 'passport.clients.update',
- ]);
-
- $router->delete('/clients/{client_id}', [
- 'uses' => 'ClientController@destroy',
- 'as' => 'passport.clients.destroy',
- ]);
- });
- }
-
- /**
- * Register the routes needed for managing personal access tokens.
- *
- * @return void
- */
- public function forPersonalAccessTokens()
- {
- $this->router->group(['middleware' => ['web', 'auth']], function ($router) {
- $router->get('/scopes', [
- 'uses' => 'ScopeController@all',
- 'as' => 'passport.scopes.index',
- ]);
-
- $router->get('/personal-access-tokens', [
- 'uses' => 'PersonalAccessTokenController@forUser',
- 'as' => 'passport.personal.tokens.index',
- ]);
-
- $router->post('/personal-access-tokens', [
- 'uses' => 'PersonalAccessTokenController@store',
- 'as' => 'passport.personal.tokens.store',
- ]);
-
- $router->delete('/personal-access-tokens/{token_id}', [
- 'uses' => 'PersonalAccessTokenController@destroy',
- 'as' => 'passport.personal.tokens.destroy',
- ]);
- });
- }
-
- /**
- * Register the routes for issuing device codes
- *
- * @return void
- */
- public function forDeviceAuthorization()
- {
- $this->router->post('/device_authorization', [
- 'uses' => 'DeviceAuthorizationController@authorize',
- 'as' => 'passport.authorizations.authorize_device',
- 'middleware' => 'throttle',
- ]);
- }
-}
diff --git a/src/Token.php b/src/Token.php
index 9dfd874c6..ad3f80817 100644
--- a/src/Token.php
+++ b/src/Token.php
@@ -6,6 +6,8 @@
class Token extends Model
{
+ use ResolvesInheritedScopes;
+
/**
* The database table used by the model.
*
@@ -42,24 +44,9 @@ class Token extends Model
protected $casts = [
'scopes' => 'array',
'revoked' => 'bool',
+ 'expires_at' => 'datetime',
];
- /**
- * The attributes that should be mutated to dates.
- *
- * @var array
- */
- protected $dates = [
- 'expires_at',
- ];
-
- /**
- * Indicates if the model should be timestamped.
- *
- * @var bool
- */
- public $timestamps = false;
-
/**
* Get the client that the token belongs to.
*
@@ -109,27 +96,6 @@ public function can($scope)
return false;
}
- /**
- * Resolve all possible scopes.
- *
- * @param string $scope
- * @return array
- */
- protected function resolveInheritedScopes($scope)
- {
- $parts = explode(':', $scope);
-
- $partsCount = count($parts);
-
- $scopes = [];
-
- for ($i = 1; $i <= $partsCount; $i++) {
- $scopes[] = implode(':', array_slice($parts, 0, $i));
- }
-
- return $scopes;
- }
-
/**
* Determine if the token is missing a given scope.
*
@@ -160,14 +126,4 @@ public function transient()
{
return false;
}
-
- /**
- * Get the current connection name for the model.
- *
- * @return string|null
- */
- public function getConnectionName()
- {
- return config('passport.storage.database.connection') ?? $this->connection;
- }
}
diff --git a/src/TokenRepository.php b/src/TokenRepository.php
index 8f992b37c..b68f339b8 100644
--- a/src/TokenRepository.php
+++ b/src/TokenRepository.php
@@ -54,7 +54,7 @@ public function forUser($userId)
/**
* Get a valid token instance for the given user and client.
*
- * @param \Illuminate\Database\Eloquent\Model $user
+ * @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param \Laravel\Passport\Client $client
* @return \Laravel\Passport\Token|null
*/
@@ -107,7 +107,7 @@ public function isAccessTokenRevoked($id)
/**
* Find a valid token for the given user and client.
*
- * @param \Illuminate\Database\Eloquent\Model $user
+ * @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param \Laravel\Passport\Client $client
* @return \Laravel\Passport\Token|null
*/
diff --git a/testbench.yaml b/testbench.yaml
new file mode 100644
index 000000000..7d0c53214
--- /dev/null
+++ b/testbench.yaml
@@ -0,0 +1,5 @@
+providers:
+ - Laravel\Passport\PassportServiceProvider
+
+migrations: true
+
diff --git a/tests/DeviceAuthorizationControllerTest.php b/tests/DeviceAuthorizationControllerTest.php
deleted file mode 100644
index 5cf6a5a8e..000000000
--- a/tests/DeviceAuthorizationControllerTest.php
+++ /dev/null
@@ -1,68 +0,0 @@
- 'description',
- ]);
-
- $server = m::mock(AuthorizationServer::class);
-
- $controller = new DeviceAuthorizationController($server);
-
- $server->shouldReceive('validateDeviceAuthorizationRequest')->andReturn($deviceAuthRequest = m::mock(DeviceAuthorizationRequest::class));
-
- $psrResponse = new \Zend\Diactoros\Response();
- $psrResponse->getBody()->write('response');
-
- $server->shouldReceive('completeDeviceAuthorizationRequest')
- ->with($deviceAuthRequest, m::type(ResponseInterface::class))
- ->andReturn($psrResponse);
-
- $this->assertEquals('response', $controller->authorize(m::mock(ServerRequestInterface::class))->getContent());
-
- }
-
- public function test_authorization_exceptions_are_handled()
- {
- $server = m::mock(AuthorizationServer::class);
-
- $controller = new DeviceAuthorizationController($server);
-
- $server->shouldReceive('validateDeviceAuthorizationRequest')->andThrow(LeagueException::invalidCredentials());
-
- $this->expectException(OAuthServerException::class);
-
- $controller->authorize(m::mock(ServerRequestInterface::class));
- }
-}
diff --git a/tests/Feature/AccessTokenControllerTest.php b/tests/Feature/AccessTokenControllerTest.php
index 451107d30..92218333c 100644
--- a/tests/Feature/AccessTokenControllerTest.php
+++ b/tests/Feature/AccessTokenControllerTest.php
@@ -4,61 +4,35 @@
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Hashing\Hasher;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\Schema;
use Laravel\Passport\Client;
-use Laravel\Passport\ClientRepository;
use Laravel\Passport\Database\Factories\ClientFactory;
-use Laravel\Passport\HasApiTokens;
use Laravel\Passport\Passport;
+use Laravel\Passport\PersonalAccessTokenFactory;
use Laravel\Passport\Token;
-use Laravel\Passport\TokenRepository;
-use Lcobucci\JWT\Configuration;
+use Orchestra\Testbench\Concerns\WithLaravelMigrations;
+use Workbench\Database\Factories\UserFactory;
class AccessTokenControllerTest extends PassportTestCase
{
- protected function setUp(): void
- {
- parent::setUp();
-
- Schema::create('users', function (Blueprint $table) {
- $table->increments('id');
- $table->string('email')->unique();
- $table->string('password');
- $table->dateTime('created_at');
- $table->dateTime('updated_at');
- });
- }
-
- protected function tearDown(): void
- {
- Schema::dropIfExists('users');
-
- parent::tearDown();
- }
-
- protected function getUserClass()
- {
- return User::class;
- }
+ use WithLaravelMigrations;
public function testGettingAccessTokenWithClientCredentialsGrant()
{
$this->withoutExceptionHandling();
- $user = new User();
- $user->email = 'foo@gmail.com';
- $user->password = $this->app->make(Hasher::class)->make('foobar123');
- $user->save();
+ $user = UserFactory::new()->create([
+ 'email' => 'foo@gmail.com',
+ 'password' => $this->app->make(Hasher::class)->make('foobar123'),
+ ]);
/** @var Client $client */
- $client = ClientFactory::new()->asClientCredentials()->create(['user_id' => $user->id]);
+ $client = ClientFactory::new()->asClientCredentials()->create(['user_id' => $user->getKey()]);
$response = $this->post(
'/oauth/token',
[
'grant_type' => 'client_credentials',
- 'client_id' => $client->id,
+ 'client_id' => $client->getKey(),
'client_secret' => $client->secret,
]
);
@@ -75,13 +49,10 @@ public function testGettingAccessTokenWithClientCredentialsGrant()
$this->assertArrayHasKey('expires_in', $decodedResponse);
$this->assertArrayHasKey('access_token', $decodedResponse);
$this->assertSame('Bearer', $decodedResponse['token_type']);
- $expiresInSeconds = 31536000;
+ $expiresInSeconds = 31622400;
$this->assertEqualsWithDelta($expiresInSeconds, $decodedResponse['expires_in'], 5);
- $jwtAccessToken = Configuration::forUnsecuredSigner()->parser()->parse($decodedResponse['access_token']);
- $this->assertTrue($this->app->make(ClientRepository::class)->findActive($jwtAccessToken->claims()->get('aud'))->is($client));
-
- $token = $this->app->make(TokenRepository::class)->find($jwtAccessToken->claims()->get('jti'));
+ $token = $this->app->make(PersonalAccessTokenFactory::class)->findAccessToken($decodedResponse);
$this->assertInstanceOf(Token::class, $token);
$this->assertTrue($token->client->is($client));
$this->assertFalse($token->revoked);
@@ -92,19 +63,19 @@ public function testGettingAccessTokenWithClientCredentialsGrant()
public function testGettingAccessTokenWithClientCredentialsGrantInvalidClientSecret()
{
- $user = new User();
- $user->email = 'foo@gmail.com';
- $user->password = $this->app->make(Hasher::class)->make('foobar123');
- $user->save();
+ $user = UserFactory::new()->create([
+ 'email' => 'foo@gmail.com',
+ 'password' => $this->app->make(Hasher::class)->make('foobar123'),
+ ]);
/** @var Client $client */
- $client = ClientFactory::new()->asClientCredentials()->create(['user_id' => $user->id]);
+ $client = ClientFactory::new()->asClientCredentials()->create(['user_id' => $user->getKey()]);
$response = $this->post(
'/oauth/token',
[
'grant_type' => 'client_credentials',
- 'client_id' => $client->id,
+ 'client_id' => $client->getKey(),
'client_secret' => $client->secret.'foo',
]
);
@@ -136,19 +107,19 @@ public function testGettingAccessTokenWithPasswordGrant()
$this->withoutExceptionHandling();
$password = 'foobar123';
- $user = new User();
- $user->email = 'foo@gmail.com';
- $user->password = $this->app->make(Hasher::class)->make($password);
- $user->save();
+ $user = UserFactory::new()->create([
+ 'email' => 'foo@gmail.com',
+ 'password' => $this->app->make(Hasher::class)->make($password),
+ ]);
/** @var Client $client */
- $client = ClientFactory::new()->asPasswordClient()->create(['user_id' => $user->id]);
+ $client = ClientFactory::new()->asPasswordClient()->create(['user_id' => $user->getKey()]);
$response = $this->post(
'/oauth/token',
[
'grant_type' => 'password',
- 'client_id' => $client->id,
+ 'client_id' => $client->getKey(),
'client_secret' => $client->secret,
'username' => $user->email,
'password' => $password,
@@ -168,14 +139,10 @@ public function testGettingAccessTokenWithPasswordGrant()
$this->assertArrayHasKey('access_token', $decodedResponse);
$this->assertArrayHasKey('refresh_token', $decodedResponse);
$this->assertSame('Bearer', $decodedResponse['token_type']);
- $expiresInSeconds = 31536000;
+ $expiresInSeconds = 31622400;
$this->assertEqualsWithDelta($expiresInSeconds, $decodedResponse['expires_in'], 5);
- $jwtAccessToken = Configuration::forUnsecuredSigner()->parser()->parse($decodedResponse['access_token']);
- $this->assertTrue($this->app->make(ClientRepository::class)->findActive($jwtAccessToken->claims()->get('aud'))->is($client));
- $this->assertTrue($this->app->make('auth')->createUserProvider()->retrieveById($jwtAccessToken->claims()->get('sub'))->is($user));
-
- $token = $this->app->make(TokenRepository::class)->find($jwtAccessToken->claims()->get('jti'));
+ $token = $this->app->make(PersonalAccessTokenFactory::class)->findAccessToken($decodedResponse);
$this->assertInstanceOf(Token::class, $token);
$this->assertFalse($token->revoked);
$this->assertTrue($token->user->is($user));
@@ -187,19 +154,19 @@ public function testGettingAccessTokenWithPasswordGrant()
public function testGettingAccessTokenWithPasswordGrantWithInvalidPassword()
{
$password = 'foobar123';
- $user = new User();
- $user->email = 'foo@gmail.com';
- $user->password = $this->app->make(Hasher::class)->make($password);
- $user->save();
+ $user = UserFactory::new()->create([
+ 'email' => 'foo@gmail.com',
+ 'password' => $this->app->make(Hasher::class)->make($password),
+ ]);
/** @var Client $client */
- $client = ClientFactory::new()->asPasswordClient()->create(['user_id' => $user->id]);
+ $client = ClientFactory::new()->asPasswordClient()->create(['user_id' => $user->getKey()]);
$response = $this->post(
'/oauth/token',
[
'grant_type' => 'password',
- 'client_id' => $client->id,
+ 'client_id' => $client->getKey(),
'client_secret' => $client->secret,
'username' => $user->email,
'password' => $password.'foo',
@@ -230,19 +197,19 @@ public function testGettingAccessTokenWithPasswordGrantWithInvalidPassword()
public function testGettingAccessTokenWithPasswordGrantWithInvalidClientSecret()
{
$password = 'foobar123';
- $user = new User();
- $user->email = 'foo@gmail.com';
- $user->password = $this->app->make(Hasher::class)->make($password);
- $user->save();
+ $user = UserFactory::new()->create([
+ 'email' => 'foo@gmail.com',
+ 'password' => $this->app->make(Hasher::class)->make($password),
+ ]);
/** @var Client $client */
- $client = ClientFactory::new()->asPasswordClient()->create(['user_id' => $user->id]);
+ $client = ClientFactory::new()->asPasswordClient()->create(['user_id' => $user->getKey()]);
$response = $this->post(
'/oauth/token',
[
'grant_type' => 'password',
- 'client_id' => $client->id,
+ 'client_id' => $client->getKey(),
'client_secret' => $client->secret.'foo',
'username' => $user->email,
'password' => $password,
@@ -277,19 +244,19 @@ public function testGettingCustomResponseType()
$this->withoutExceptionHandling();
Passport::$authorizationServerResponseType = new IdTokenResponse('foo_bar_open_id_token');
- $user = new User();
- $user->email = 'foo@gmail.com';
- $user->password = $this->app->make(Hasher::class)->make('foobar123');
- $user->save();
+ $user = UserFactory::new()->create([
+ 'email' => 'foo@gmail.com',
+ 'password' => $this->app->make(Hasher::class)->make('foobar123'),
+ ]);
/** @var Client $client */
- $client = ClientFactory::new()->asClientCredentials()->create(['user_id' => $user->id]);
+ $client = ClientFactory::new()->asClientCredentials()->create(['user_id' => $user->getKey()]);
$response = $this->post(
'/oauth/token',
[
'grant_type' => 'client_credentials',
- 'client_id' => $client->id,
+ 'client_id' => $client->getKey(),
'client_secret' => $client->secret,
]
);
@@ -303,11 +270,6 @@ public function testGettingCustomResponseType()
}
}
-class User extends \Illuminate\Foundation\Auth\User
-{
- use HasApiTokens;
-}
-
class IdTokenResponse extends \League\OAuth2\Server\ResponseTypes\BearerTokenResponse
{
/**
diff --git a/tests/Feature/ActingAsClientTest.php b/tests/Feature/ActingAsClientTest.php
index 8fc99b777..f0a92c7d7 100644
--- a/tests/Feature/ActingAsClientTest.php
+++ b/tests/Feature/ActingAsClientTest.php
@@ -7,9 +7,8 @@
use Laravel\Passport\Http\Middleware\CheckClientCredentials;
use Laravel\Passport\Http\Middleware\CheckClientCredentialsForAnyScope;
use Laravel\Passport\Passport;
-use Orchestra\Testbench\TestCase;
-class ActingAsClientTest extends TestCase
+class ActingAsClientTest extends PassportTestCase
{
public function testActingAsClientWhenTheRouteIsProtectedByCheckClientCredentialsMiddleware()
{
@@ -46,4 +45,11 @@ public function testActingAsClientWhenTheRouteIsProtectedByCheckClientCredential
$response->assertSuccessful();
$response->assertSee('bar');
}
+
+ public function testActingAsClientSetsTheClientOnTheGuard()
+ {
+ Passport::actingAsClient($client = new Client());
+
+ $this->assertSame($client, app('auth')->client());
+ }
}
diff --git a/tests/Feature/ActingAsTest.php b/tests/Feature/ActingAsTest.php
index aa1b3ecea..d67f93798 100644
--- a/tests/Feature/ActingAsTest.php
+++ b/tests/Feature/ActingAsTest.php
@@ -3,11 +3,11 @@
namespace Laravel\Passport\Tests\Feature;
use Illuminate\Contracts\Routing\Registrar;
-use Illuminate\Foundation\Auth\User;
-use Laravel\Passport\HasApiTokens;
+use Illuminate\Support\Facades\Route;
use Laravel\Passport\Http\Middleware\CheckForAnyScope;
use Laravel\Passport\Http\Middleware\CheckScopes;
use Laravel\Passport\Passport;
+use Workbench\App\Models\User;
class ActingAsTest extends PassportTestCase
{
@@ -22,7 +22,7 @@ public function testActingAsWhenTheRouteIsProtectedByAuthMiddleware()
return 'bar';
})->middleware('auth:api');
- Passport::actingAs(new PassportUser());
+ Passport::actingAs(new User());
$response = $this->get('/foo');
$response->assertSuccessful();
@@ -40,13 +40,28 @@ public function testActingAsWhenTheRouteIsProtectedByCheckScopesMiddleware()
return 'bar';
})->middleware(CheckScopes::class.':admin,footest');
- Passport::actingAs(new PassportUser(), ['admin', 'footest']);
+ Passport::actingAs(new User(), ['admin', 'footest']);
$response = $this->get('/foo');
$response->assertSuccessful();
$response->assertSee('bar');
}
+ public function testItCanGenerateDefinitionViaStaticMethod()
+ {
+ $signature = (string) CheckScopes::using('admin');
+ $this->assertSame('Laravel\Passport\Http\Middleware\CheckScopes:admin', $signature);
+
+ $signature = (string) CheckScopes::using('admin', 'footest');
+ $this->assertSame('Laravel\Passport\Http\Middleware\CheckScopes:admin,footest', $signature);
+
+ $signature = (string) CheckForAnyScope::using('admin');
+ $this->assertSame('Laravel\Passport\Http\Middleware\CheckForAnyScope:admin', $signature);
+
+ $signature = (string) CheckForAnyScope::using('admin', 'footest');
+ $this->assertSame('Laravel\Passport\Http\Middleware\CheckForAnyScope:admin,footest', $signature);
+ }
+
public function testActingAsWhenTheRouteIsProtectedByCheckForAnyScopeMiddleware()
{
$this->withoutExceptionHandling();
@@ -58,17 +73,44 @@ public function testActingAsWhenTheRouteIsProtectedByCheckForAnyScopeMiddleware(
return 'bar';
})->middleware(CheckForAnyScope::class.':admin,footest');
- Passport::actingAs(new PassportUser(), ['footest']);
+ Passport::actingAs(new User(), ['footest']);
$response = $this->get('/foo');
$response->assertSuccessful();
$response->assertSee('bar');
}
-}
-class PassportUser extends User
-{
- use HasApiTokens;
+ public function testActingAsWhenTheRouteIsProtectedByCheckScopesMiddlewareWithInheritance()
+ {
+ Passport::$withInheritedScopes = true;
+
+ $this->withoutExceptionHandling();
+
+ Route::middleware(CheckScopes::class.':foo:bar,baz:qux')->get('/foo', function () {
+ return 'bar';
+ });
+
+ Passport::actingAs(new User(), ['foo', 'baz']);
+
+ $response = $this->get('/foo');
+ $response->assertSuccessful();
+ $response->assertSee('bar');
+ }
+
+ public function testActingAsWhenTheRouteIsProtectedByCheckForAnyScopeMiddlewareWithInheritance()
+ {
+ Passport::$withInheritedScopes = true;
+
+ $this->withoutExceptionHandling();
- protected $table = 'users';
+ Route::middleware(CheckForAnyScope::class.':foo:baz,baz:qux')->get('/foo', function () {
+ return 'bar';
+ });
+
+ Passport::actingAs(new User(), ['foo']);
+
+ $response = $this->get('/foo');
+ $response->assertSuccessful();
+ $response->assertSee('bar');
+ }
}
diff --git a/tests/Feature/KeysCommandTest.php b/tests/Feature/KeysCommandTest.php
index e7d528f82..3d2f4b986 100644
--- a/tests/Feature/KeysCommandTest.php
+++ b/tests/Feature/KeysCommandTest.php
@@ -2,18 +2,8 @@
namespace Laravel\Passport\Tests\Feature;
-use Mockery as m;
-
class KeysCommandTest extends PassportTestCase
{
- protected function tearDown(): void
- {
- m::close();
-
- @unlink(self::PUBLIC_KEY);
- @unlink(self::PRIVATE_KEY);
- }
-
public function testPrivateAndPublicKeysAreGenerated()
{
$this->assertFileExists(self::PUBLIC_KEY);
diff --git a/tests/Feature/PassportTestCase.php b/tests/Feature/PassportTestCase.php
index 610bfef37..3ccf02bdc 100644
--- a/tests/Feature/PassportTestCase.php
+++ b/tests/Feature/PassportTestCase.php
@@ -3,14 +3,15 @@
namespace Laravel\Passport\Tests\Feature;
use Illuminate\Contracts\Config\Repository;
-use Illuminate\Foundation\Testing\RefreshDatabase;
+use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Laravel\Passport\Passport;
-use Laravel\Passport\PassportServiceProvider;
+use Orchestra\Testbench\Concerns\WithWorkbench;
use Orchestra\Testbench\TestCase;
+use Workbench\App\Models\User;
abstract class PassportTestCase extends TestCase
{
- use RefreshDatabase;
+ use LazilyRefreshDatabase, WithWorkbench;
const KEYS = __DIR__.'/../keys';
const PUBLIC_KEY = self::KEYS.'/oauth-public.key';
@@ -18,54 +19,32 @@ abstract class PassportTestCase extends TestCase
protected function setUp(): void
{
- parent::setUp();
-
- $this->artisan('migrate:fresh');
+ $this->afterApplicationCreated(function () {
+ Passport::loadKeysFrom(self::KEYS);
- Passport::routes();
+ @unlink(self::PUBLIC_KEY);
+ @unlink(self::PRIVATE_KEY);
- Passport::loadKeysFrom(self::KEYS);
+ $this->artisan('passport:keys');
+ });
- @unlink(self::PUBLIC_KEY);
- @unlink(self::PRIVATE_KEY);
+ $this->beforeApplicationDestroyed(function () {
+ @unlink(self::PUBLIC_KEY);
+ @unlink(self::PRIVATE_KEY);
+ });
- $this->artisan('passport:keys');
+ parent::setUp();
}
- protected function getEnvironmentSetUp($app)
+ protected function defineEnvironment($app)
{
$config = $app->make(Repository::class);
- $config->set('auth.defaults.provider', 'users');
-
- if (($userClass = $this->getUserClass()) !== null) {
- $config->set('auth.providers.users.model', $userClass);
- }
-
- $config->set('auth.guards.api', ['driver' => 'passport', 'provider' => 'users']);
-
- $app['config']->set('database.default', 'testbench');
-
- $app['config']->set('passport.storage.database.connection', 'testbench');
-
- $app['config']->set('database.connections.testbench', [
- 'driver' => 'sqlite',
- 'database' => ':memory:',
- 'prefix' => '',
+ $config->set([
+ 'auth.defaults.provider' => 'users',
+ 'auth.providers.users.model' => User::class,
+ 'auth.guards.api' => ['driver' => 'passport', 'provider' => 'users'],
+ 'database.default' => 'testing',
]);
}
-
- protected function getPackageProviders($app)
- {
- return [PassportServiceProvider::class];
- }
-
- /**
- * Get the Eloquent user model class name.
- *
- * @return string|null
- */
- protected function getUserClass()
- {
- }
}
diff --git a/tests/Unit/AccessTokenControllerTest.php b/tests/Unit/AccessTokenControllerTest.php
index 0e451fc97..bcd72cb55 100644
--- a/tests/Unit/AccessTokenControllerTest.php
+++ b/tests/Unit/AccessTokenControllerTest.php
@@ -5,7 +5,6 @@
use Laravel\Passport\Exceptions\OAuthServerException;
use Laravel\Passport\Http\Controllers\AccessTokenController;
use Laravel\Passport\TokenRepository;
-use Lcobucci\JWT\Parser;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException as LeagueException;
use Mockery as m;
@@ -26,7 +25,6 @@ public function test_a_token_can_be_issued()
$request = m::mock(ServerRequestInterface::class);
$response = m::type(ResponseInterface::class);
$tokens = m::mock(TokenRepository::class);
- $jwt = m::mock(Parser::class);
$psrResponse = new Response();
$psrResponse->getBody()->write(json_encode(['access_token' => 'access-token']));
@@ -36,7 +34,7 @@ public function test_a_token_can_be_issued()
->with($request, $response)
->andReturn($psrResponse);
- $controller = new AccessTokenController($server, $tokens, $jwt);
+ $controller = new AccessTokenController($server, $tokens);
$this->assertSame('{"access_token":"access-token"}', $controller->issueToken($request)->getContent());
}
@@ -44,14 +42,13 @@ public function test_a_token_can_be_issued()
public function test_exceptions_are_handled()
{
$tokens = m::mock(TokenRepository::class);
- $jwt = m::mock(Parser::class);
$server = m::mock(AuthorizationServer::class);
$server->shouldReceive('respondToAccessTokenRequest')->with(
m::type(ServerRequestInterface::class), m::type(ResponseInterface::class)
)->andThrow(LeagueException::invalidCredentials());
- $controller = new AccessTokenController($server, $tokens, $jwt);
+ $controller = new AccessTokenController($server, $tokens);
$this->expectException(OAuthServerException::class);
diff --git a/tests/Unit/AuthorizationControllerTest.php b/tests/Unit/AuthorizationControllerTest.php
index a8672cf10..fe6e7f3ea 100644
--- a/tests/Unit/AuthorizationControllerTest.php
+++ b/tests/Unit/AuthorizationControllerTest.php
@@ -2,13 +2,15 @@
namespace Laravel\Passport\Tests\Unit;
-use Illuminate\Contracts\Routing\ResponseFactory;
+use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\Request;
use Laravel\Passport\Bridge\Scope;
use Laravel\Passport\Client;
use Laravel\Passport\ClientRepository;
+use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\Exceptions\OAuthServerException;
use Laravel\Passport\Http\Controllers\AuthorizationController;
+use Laravel\Passport\Http\Responses\AuthorizationViewResponse;
use Laravel\Passport\Passport;
use Laravel\Passport\Token;
use Laravel\Passport\TokenRepository;
@@ -35,38 +37,41 @@ public function test_authorization_view_is_presented()
]);
$server = m::mock(AuthorizationServer::class);
- $response = m::mock(ResponseFactory::class);
+ $response = m::mock(AuthorizationViewResponse::class);
+ $guard = m::mock(StatefulGuard::class);
- $controller = new AuthorizationController($server, $response);
+ $controller = new AuthorizationController($server, $guard, $response);
+ $guard->shouldReceive('guest')->andReturn(false);
+ $guard->shouldReceive('user')->andReturn($user = m::mock());
$server->shouldReceive('validateAuthorizationRequest')->andReturn($authRequest = m::mock());
$request = m::mock(Request::class);
$request->shouldReceive('session')->andReturn($session = m::mock());
$session->shouldReceive('put')->withSomeOfArgs('authToken');
$session->shouldReceive('put')->with('authRequest', $authRequest);
- $request->shouldReceive('user')->andReturn($user = m::mock());
+ $session->shouldReceive('forget')->with('promptedForLogin')->once();
+ $request->shouldReceive('get')->with('prompt')->andReturn(null);
$authRequest->shouldReceive('getClient->getIdentifier')->andReturn(1);
$authRequest->shouldReceive('getScopes')->andReturn([new Scope('scope-1')]);
$clients = m::mock(ClientRepository::class);
$clients->shouldReceive('find')->with(1)->andReturn($client = m::mock(Client::class));
-
$client->shouldReceive('skipsAuthorization')->andReturn(false);
- $response->shouldReceive('view')->once()->andReturnUsing(function ($view, $data) use ($client, $user) {
- $this->assertSame('passport::authorize', $view);
+ $tokens = m::mock(TokenRepository::class);
+ $tokens->shouldReceive('findValidToken')->with($user, $client)->andReturnNull();
+
+ $response->shouldReceive('withParameters')->once()->andReturnUsing(function ($data) use ($client, $user, $request) {
$this->assertEquals($client, $data['client']);
$this->assertEquals($user, $data['user']);
+ $this->assertEquals($request, $data['request']);
$this->assertSame('description', $data['scopes'][0]->description);
return 'view';
});
- $tokens = m::mock(TokenRepository::class);
- $tokens->shouldReceive('findValidToken')->with($user, $client)->andReturnNull();
-
$this->assertSame('view', $controller->authorize(
m::mock(ServerRequestInterface::class), $request, $clients, $tokens
));
@@ -75,10 +80,12 @@ public function test_authorization_view_is_presented()
public function test_authorization_exceptions_are_handled()
{
$server = m::mock(AuthorizationServer::class);
- $response = m::mock(ResponseFactory::class);
+ $response = m::mock(AuthorizationViewResponse::class);
+ $guard = m::mock(StatefulGuard::class);
- $controller = new AuthorizationController($server, $response);
+ $controller = new AuthorizationController($server, $guard, $response);
+ $guard->shouldReceive('guest')->andReturn(false);
$server->shouldReceive('validateAuthorizationRequest')->andThrow(LeagueException::invalidCredentials());
$request = m::mock(Request::class);
@@ -101,9 +108,13 @@ public function test_request_is_approved_if_valid_token_exists()
]);
$server = m::mock(AuthorizationServer::class);
- $response = m::mock(ResponseFactory::class);
+ $response = m::mock(AuthorizationViewResponse::class);
+ $guard = m::mock(StatefulGuard::class);
- $controller = new AuthorizationController($server, $response);
+ $controller = new AuthorizationController($server, $guard, $response);
+
+ $guard->shouldReceive('guest')->andReturn(false);
+ $guard->shouldReceive('user')->andReturn($user = m::mock());
$psrResponse = new Response();
$psrResponse->getBody()->write('approved');
$server->shouldReceive('validateAuthorizationRequest')
@@ -113,9 +124,11 @@ public function test_request_is_approved_if_valid_token_exists()
->andReturn($psrResponse);
$request = m::mock(Request::class);
- $request->shouldReceive('user')->once()->andReturn($user = m::mock());
+ $request->shouldReceive('session')->andReturn($session = m::mock());
+ $session->shouldReceive('forget')->with('promptedForLogin')->once();
$user->shouldReceive('getAuthIdentifier')->andReturn(1);
$request->shouldNotReceive('session');
+ $request->shouldReceive('get')->with('prompt')->andReturn(null);
$authRequest->shouldReceive('getClient->getIdentifier')->once()->andReturn(1);
$authRequest->shouldReceive('getScopes')->once()->andReturn([new Scope('scope-1')]);
@@ -123,11 +136,13 @@ public function test_request_is_approved_if_valid_token_exists()
$authRequest->shouldReceive('setAuthorizationApproved')->once()->with(true);
$clients = m::mock(ClientRepository::class);
- $clients->shouldReceive('find')->with(1)->andReturn('client');
+ $clients->shouldReceive('find')->with(1)->andReturn($client = m::mock(Client::class));
+
+ $client->shouldReceive('skipsAuthorization')->andReturn(false);
$tokens = m::mock(TokenRepository::class);
$tokens->shouldReceive('findValidToken')
- ->with($user, 'client')
+ ->with($user, $client)
->andReturn($token = m::mock(Token::class));
$token->shouldReceive('getAttribute')->with('scopes')->andReturn(['scope-1']);
@@ -143,9 +158,13 @@ public function test_request_is_approved_if_client_can_skip_authorization()
]);
$server = m::mock(AuthorizationServer::class);
- $response = m::mock(ResponseFactory::class);
+ $response = m::mock(AuthorizationViewResponse::class);
+ $guard = m::mock(StatefulGuard::class);
+
+ $controller = new AuthorizationController($server, $guard, $response);
- $controller = new AuthorizationController($server, $response);
+ $guard->shouldReceive('guest')->andReturn(false);
+ $guard->shouldReceive('user')->andReturn($user = m::mock());
$psrResponse = new Response();
$psrResponse->getBody()->write('approved');
$server->shouldReceive('validateAuthorizationRequest')
@@ -155,9 +174,11 @@ public function test_request_is_approved_if_client_can_skip_authorization()
->andReturn($psrResponse);
$request = m::mock(Request::class);
- $request->shouldReceive('user')->once()->andReturn($user = m::mock());
+ $request->shouldReceive('session')->andReturn($session = m::mock());
+ $session->shouldReceive('forget')->with('promptedForLogin')->once();
$user->shouldReceive('getAuthIdentifier')->andReturn(1);
$request->shouldNotReceive('session');
+ $request->shouldReceive('get')->with('prompt')->andReturn(null);
$authRequest->shouldReceive('getClient->getIdentifier')->once()->andReturn(1);
$authRequest->shouldReceive('getScopes')->once()->andReturn([new Scope('scope-1')]);
@@ -178,4 +199,198 @@ public function test_request_is_approved_if_client_can_skip_authorization()
m::mock(ServerRequestInterface::class), $request, $clients, $tokens
)->getContent());
}
+
+ public function test_authorization_view_is_presented_if_request_has_prompt_equals_to_consent()
+ {
+ Passport::tokensCan([
+ 'scope-1' => 'description',
+ ]);
+
+ $server = m::mock(AuthorizationServer::class);
+ $response = m::mock(AuthorizationViewResponse::class);
+ $guard = m::mock(StatefulGuard::class);
+
+ $controller = new AuthorizationController($server, $guard, $response);
+
+ $guard->shouldReceive('guest')->andReturn(false);
+ $guard->shouldReceive('user')->andReturn($user = m::mock());
+ $server->shouldReceive('validateAuthorizationRequest')
+ ->andReturn($authRequest = m::mock(AuthorizationRequest::class));
+
+ $request = m::mock(Request::class);
+ $request->shouldReceive('session')->andReturn($session = m::mock());
+ $session->shouldReceive('put')->withSomeOfArgs('authToken');
+ $session->shouldReceive('put')->with('authRequest', $authRequest);
+ $session->shouldReceive('forget')->with('promptedForLogin')->once();
+ $request->shouldReceive('get')->with('prompt')->andReturn('consent');
+
+ $authRequest->shouldReceive('getClient->getIdentifier')->once()->andReturn(1);
+ $authRequest->shouldReceive('getScopes')->once()->andReturn([new Scope('scope-1')]);
+
+ $clients = m::mock(ClientRepository::class);
+ $clients->shouldReceive('find')->with(1)->andReturn($client = m::mock(Client::class));
+ $client->shouldReceive('skipsAuthorization')->andReturn(false);
+
+ $tokens = m::mock(TokenRepository::class);
+ $tokens->shouldNotReceive('findValidToken');
+
+ $response->shouldReceive('withParameters')->once()->andReturnUsing(function ($data) use ($client, $user, $request) {
+ $this->assertEquals($client, $data['client']);
+ $this->assertEquals($user, $data['user']);
+ $this->assertEquals($request, $data['request']);
+ $this->assertSame('description', $data['scopes'][0]->description);
+
+ return 'view';
+ });
+
+ $this->assertSame('view', $controller->authorize(
+ m::mock(ServerRequestInterface::class), $request, $clients, $tokens
+ ));
+ }
+
+ public function test_authorization_denied_if_request_has_prompt_equals_to_none()
+ {
+ $this->expectException('Laravel\Passport\Exceptions\OAuthServerException');
+
+ Passport::tokensCan([
+ 'scope-1' => 'description',
+ ]);
+
+ $server = m::mock(AuthorizationServer::class);
+ $response = m::mock(AuthorizationViewResponse::class);
+ $guard = m::mock(StatefulGuard::class);
+
+ $controller = new AuthorizationController($server, $guard, $response);
+
+ $guard->shouldReceive('guest')->andReturn(false);
+ $guard->shouldReceive('user')->andReturn($user = m::mock());
+ $server->shouldReceive('validateAuthorizationRequest')
+ ->andReturn($authRequest = m::mock(AuthorizationRequest::class));
+ $server->shouldReceive('completeAuthorizationRequest')
+ ->with($authRequest, m::type(ResponseInterface::class))
+ ->once()
+ ->andThrow('League\OAuth2\Server\Exception\OAuthServerException');
+
+ $request = m::mock(Request::class);
+ $request->shouldReceive('session')->andReturn($session = m::mock());
+ $session->shouldReceive('forget')->with('promptedForLogin')->once();
+ $user->shouldReceive('getAuthIdentifier')->andReturn(1);
+ $request->shouldReceive('get')->with('prompt')->andReturn('none');
+
+ $authRequest->shouldReceive('getClient->getIdentifier')->once()->andReturn(1);
+ $authRequest->shouldReceive('getScopes')->once()->andReturn([new Scope('scope-1')]);
+ $authRequest->shouldReceive('setUser')->once()->andReturnNull();
+ $authRequest->shouldReceive('setAuthorizationApproved')->once()->with(false);
+
+ $clients = m::mock(ClientRepository::class);
+ $clients->shouldReceive('find')->with(1)->andReturn($client = m::mock(Client::class));
+ $client->shouldReceive('skipsAuthorization')->andReturn(false);
+
+ $tokens = m::mock(TokenRepository::class);
+ $tokens->shouldReceive('findValidToken')
+ ->with($user, $client)
+ ->andReturnNull();
+
+ $controller->authorize(
+ m::mock(ServerRequestInterface::class), $request, $clients, $tokens
+ );
+ }
+
+ public function test_authorization_denied_if_unauthenticated_and_request_has_prompt_equals_to_none()
+ {
+ $server = m::mock(AuthorizationServer::class);
+ $response = m::mock(AuthorizationViewResponse::class);
+ $guard = m::mock(StatefulGuard::class);
+
+ $controller = new AuthorizationController($server, $guard, $response);
+
+ $guard->shouldReceive('guest')->andReturn(true);
+ $server->shouldReceive('validateAuthorizationRequest')
+ ->andReturn($authRequest = m::mock(AuthorizationRequest::class));
+ $server->shouldNotReceive('completeAuthorizationRequest');
+
+ $request = m::mock(Request::class);
+ $request->shouldNotReceive('user');
+ $request->shouldReceive('get')->with('prompt')->andReturn('none');
+
+ $authRequest->shouldNotReceive('setUser');
+ $authRequest->shouldReceive('setAuthorizationApproved')->with(false);
+ $authRequest->shouldReceive('getRedirectUri')->andReturn('http://localhost');
+ $authRequest->shouldReceive('getClient->getRedirectUri')->andReturn('http://localhost');
+ $authRequest->shouldReceive('getState')->andReturn('state');
+ $authRequest->shouldReceive('getGrantTypeId')->andReturn('authorization_code');
+
+ $clients = m::mock(ClientRepository::class);
+ $tokens = m::mock(TokenRepository::class);
+
+ try {
+ $controller->authorize(
+ m::mock(ServerRequestInterface::class), $request, $clients, $tokens
+ );
+ } catch (\Laravel\Passport\Exceptions\OAuthServerException $e) {
+ $this->assertStringStartsWith(
+ 'http://localhost?state=state&error=access_denied&error_description=',
+ $e->render($request)->headers->get('location')
+ );
+ }
+ }
+
+ public function test_logout_and_prompt_login_if_request_has_prompt_equals_to_login()
+ {
+ $this->expectException(AuthenticationException::class);
+
+ $server = m::mock(AuthorizationServer::class);
+ $response = m::mock(AuthorizationViewResponse::class);
+ $guard = m::mock(StatefulGuard::class);
+
+ $controller = new AuthorizationController($server, $guard, $response);
+
+ $guard->shouldReceive('guest')->andReturn(false);
+ $server->shouldReceive('validateAuthorizationRequest')->once();
+ $guard->shouldReceive('logout')->once();
+
+ $request = m::mock(Request::class);
+ $request->shouldReceive('session')->andReturn($session = m::mock());
+ $session->shouldReceive('invalidate')->once();
+ $session->shouldReceive('regenerateToken')->once();
+ $session->shouldReceive('get')->with('promptedForLogin', false)->once()->andReturn(false);
+ $session->shouldReceive('put')->with('promptedForLogin', true)->once();
+ $session->shouldNotReceive('forget')->with('promptedForLogin');
+ $request->shouldReceive('get')->with('prompt')->andReturn('login');
+
+ $clients = m::mock(ClientRepository::class);
+ $tokens = m::mock(TokenRepository::class);
+
+ $controller->authorize(
+ m::mock(ServerRequestInterface::class), $request, $clients, $tokens
+ );
+ }
+
+ public function test_user_should_be_authenticated()
+ {
+ $this->expectException(AuthenticationException::class);
+
+ $server = m::mock(AuthorizationServer::class);
+ $response = m::mock(AuthorizationViewResponse::class);
+ $guard = m::mock(StatefulGuard::class);
+
+ $controller = new AuthorizationController($server, $guard, $response);
+
+ $guard->shouldReceive('guest')->andReturn(true);
+ $server->shouldReceive('validateAuthorizationRequest')->once();
+
+ $request = m::mock(Request::class);
+ $request->shouldNotReceive('user');
+ $request->shouldReceive('session')->andReturn($session = m::mock());
+ $session->shouldReceive('put')->with('promptedForLogin', true)->once();
+ $session->shouldNotReceive('forget')->with('promptedForLogin');
+ $request->shouldReceive('get')->with('prompt')->andReturn(null);
+
+ $clients = m::mock(ClientRepository::class);
+ $tokens = m::mock(TokenRepository::class);
+
+ $controller->authorize(
+ m::mock(ServerRequestInterface::class), $request, $clients, $tokens
+ );
+ }
}
diff --git a/tests/Unit/BridgeAccessTokenRepositoryTest.php b/tests/Unit/BridgeAccessTokenRepositoryTest.php
index 0d34d9b45..aa99cd051 100644
--- a/tests/Unit/BridgeAccessTokenRepositoryTest.php
+++ b/tests/Unit/BridgeAccessTokenRepositoryTest.php
@@ -3,6 +3,7 @@
namespace Laravel\Passport\Tests\Unit;
use Carbon\CarbonImmutable;
+use DateTime;
use Illuminate\Contracts\Events\Dispatcher;
use Laravel\Passport\Bridge\AccessToken;
use Laravel\Passport\Bridge\AccessTokenRepository;
@@ -32,8 +33,8 @@ public function test_access_tokens_can_be_persisted()
$this->assertSame('client-id', $array['client_id']);
$this->assertEquals(['scopes'], $array['scopes']);
$this->assertEquals(false, $array['revoked']);
- $this->assertInstanceOf('DateTime', $array['created_at']);
- $this->assertInstanceOf('DateTime', $array['updated_at']);
+ $this->assertInstanceOf(DateTime::class, $array['created_at']);
+ $this->assertInstanceOf(DateTime::class, $array['updated_at']);
$this->assertEquals($expiration, $array['expires_at']);
});
diff --git a/tests/Unit/BridgeScopeRepositoryTest.php b/tests/Unit/BridgeScopeRepositoryTest.php
index 4dc370aca..e663bf89b 100644
--- a/tests/Unit/BridgeScopeRepositoryTest.php
+++ b/tests/Unit/BridgeScopeRepositoryTest.php
@@ -5,18 +5,90 @@
use Laravel\Passport\Bridge\Client;
use Laravel\Passport\Bridge\Scope;
use Laravel\Passport\Bridge\ScopeRepository;
+use Laravel\Passport\Client as ClientModel;
+use Laravel\Passport\ClientRepository;
use Laravel\Passport\Passport;
+use Mockery;
use PHPUnit\Framework\TestCase;
class BridgeScopeRepositoryTest extends TestCase
{
+ protected function tearDown(): void
+ {
+ Passport::$withInheritedScopes = false;
+ }
+
public function test_invalid_scopes_are_removed()
{
Passport::tokensCan([
'scope-1' => 'description',
]);
- $repository = new ScopeRepository;
+ $client = Mockery::mock(ClientModel::class)->makePartial();
+
+ $clients = Mockery::mock(ClientRepository::class);
+ $clients->shouldReceive('findActive')->withAnyArgs()->andReturn($client);
+
+ $repository = new ScopeRepository($clients);
+
+ $scopes = $repository->finalizeScopes(
+ [$scope1 = new Scope('scope-1'), new Scope('scope-2')], 'client_credentials', new Client('id', 'name', 'http://localhost'), 1
+ );
+
+ $this->assertEquals([$scope1], $scopes);
+ }
+
+ public function test_invalid_scopes_are_removed_without_a_client_repository()
+ {
+ Passport::tokensCan([
+ 'scope-1' => 'description',
+ ]);
+
+ $repository = new ScopeRepository();
+
+ $scopes = $repository->finalizeScopes(
+ [$scope1 = new Scope('scope-1'), new Scope('scope-2')], 'client_credentials', new Client('id', 'name', 'http://localhost'), 1
+ );
+
+ $this->assertEquals([$scope1], $scopes);
+ }
+
+ public function test_clients_do_not_restrict_scopes_by_default()
+ {
+ Passport::tokensCan([
+ 'scope-1' => 'description',
+ 'scope-2' => 'description',
+ ]);
+
+ $client = Mockery::mock(ClientModel::class)->makePartial();
+ $client->scopes = null;
+
+ $clients = Mockery::mock(ClientRepository::class);
+ $clients->shouldReceive('findActive')->withAnyArgs()->andReturn($client);
+
+ $repository = new ScopeRepository($clients);
+
+ $scopes = $repository->finalizeScopes(
+ [$scope1 = new Scope('scope-1'), $scope2 = new Scope('scope-2')], 'client_credentials', new Client('id', 'name', 'http://localhost'), 1
+ );
+
+ $this->assertEquals([$scope1, $scope2], $scopes);
+ }
+
+ public function test_scopes_disallowed_for_client_are_removed()
+ {
+ Passport::tokensCan([
+ 'scope-1' => 'description',
+ 'scope-2' => 'description',
+ ]);
+
+ $client = Mockery::mock(ClientModel::class)->makePartial();
+ $client->scopes = ['scope-1'];
+
+ $clients = Mockery::mock(ClientRepository::class);
+ $clients->shouldReceive('findActive')->withAnyArgs()->andReturn($client);
+
+ $repository = new ScopeRepository($clients);
$scopes = $repository->finalizeScopes(
[$scope1 = new Scope('scope-1'), new Scope('scope-2')], 'client_credentials', new Client('id', 'name', 'http://localhost'), 1
@@ -25,13 +97,58 @@ public function test_invalid_scopes_are_removed()
$this->assertEquals([$scope1], $scopes);
}
+ public function test_scopes_disallowed_for_client_are_removed_but_inherited_scopes_are_not()
+ {
+ Passport::$withInheritedScopes = true;
+
+ Passport::tokensCan([
+ 'scope-1' => 'description',
+ 'scope-1:limited-access' => 'description',
+ 'scope-2' => 'description',
+ ]);
+
+ $client = Mockery::mock(ClientModel::class)->makePartial();
+ $client->scopes = ['scope-1'];
+
+ $clients = Mockery::mock(ClientRepository::class);
+ $clients->shouldReceive('findActive')->withAnyArgs()->andReturn($client);
+
+ $repository = new ScopeRepository($clients);
+
+ $scopes = $repository->finalizeScopes(
+ [$scope1 = new Scope('scope-1:limited-access'), new Scope('scope-2')], 'client_credentials', new Client('id', 'name', 'http://localhost'), 1
+ );
+
+ $this->assertEquals([$scope1], $scopes);
+ }
+
public function test_superuser_scope_cant_be_applied_if_wrong_grant()
{
Passport::tokensCan([
'scope-1' => 'description',
]);
- $repository = new ScopeRepository;
+ $client = Mockery::mock(ClientModel::class)->makePartial();
+
+ $clients = Mockery::mock(ClientRepository::class);
+ $clients->shouldReceive('findActive')->withAnyArgs()->andReturn($client);
+
+ $repository = new ScopeRepository($clients);
+
+ $scopes = $repository->finalizeScopes(
+ [$scope1 = new Scope('*')], 'refresh_token', new Client('id', 'name', 'http://localhost'), 1
+ );
+
+ $this->assertEquals([], $scopes);
+ }
+
+ public function test_superuser_scope_cant_be_applied_if_wrong_grant_without_a_client_repository()
+ {
+ Passport::tokensCan([
+ 'scope-1' => 'description',
+ ]);
+
+ $repository = new ScopeRepository();
$scopes = $repository->finalizeScopes(
[$scope1 = new Scope('*')], 'refresh_token', new Client('id', 'name', 'http://localhost'), 1
diff --git a/tests/Unit/CheckClientCredentialsForAnyScopeTest.php b/tests/Unit/CheckClientCredentialsForAnyScopeTest.php
index 754d9a7de..807bf93d4 100644
--- a/tests/Unit/CheckClientCredentialsForAnyScopeTest.php
+++ b/tests/Unit/CheckClientCredentialsForAnyScopeTest.php
@@ -4,6 +4,7 @@
use Illuminate\Http\Request;
use Laravel\Passport\Client;
+use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\Http\Middleware\CheckClientCredentialsForAnyScope;
use Laravel\Passport\Token;
use Laravel\Passport\TokenRepository;
@@ -85,7 +86,7 @@ public function test_request_is_passed_along_if_token_has_any_required_scope()
public function test_exception_is_thrown_when_oauth_throws_exception()
{
- $this->expectException('Illuminate\Auth\AuthenticationException');
+ $this->expectException(AuthenticationException::class);
$tokenRepository = m::mock(TokenRepository::class);
$resourceServer = m::mock(ResourceServer::class);
diff --git a/tests/Unit/CheckClientCredentialsTest.php b/tests/Unit/CheckClientCredentialsTest.php
index 36e410dd5..0a30b19ea 100644
--- a/tests/Unit/CheckClientCredentialsTest.php
+++ b/tests/Unit/CheckClientCredentialsTest.php
@@ -4,6 +4,7 @@
use Illuminate\Http\Request;
use Laravel\Passport\Client;
+use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\Http\Middleware\CheckClientCredentials;
use Laravel\Passport\Token;
use Laravel\Passport\TokenRepository;
@@ -84,7 +85,7 @@ public function test_request_is_passed_along_if_token_and_scope_are_valid()
public function test_exception_is_thrown_when_oauth_throws_exception()
{
- $this->expectException('Illuminate\Auth\AuthenticationException');
+ $this->expectException(AuthenticationException::class);
$tokenRepository = m::mock(TokenRepository::class);
$resourceServer = m::mock(ResourceServer::class);
diff --git a/tests/Unit/CheckForAnyScopeTest.php b/tests/Unit/CheckForAnyScopeTest.php
index cf9e04c5b..e567feeb0 100644
--- a/tests/Unit/CheckForAnyScopeTest.php
+++ b/tests/Unit/CheckForAnyScopeTest.php
@@ -2,6 +2,7 @@
namespace Laravel\Passport\Tests\Unit;
+use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\Http\Middleware\CheckForAnyScope as CheckScopes;
use Mockery as m;
use PHPUnit\Framework\TestCase;
@@ -47,7 +48,7 @@ public function test_exception_is_thrown_if_token_doesnt_have_scope()
public function test_exception_is_thrown_if_no_authenticated_user()
{
- $this->expectException('Illuminate\Auth\AuthenticationException');
+ $this->expectException(AuthenticationException::class);
$middleware = new CheckScopes;
$request = m::mock();
@@ -60,7 +61,7 @@ public function test_exception_is_thrown_if_no_authenticated_user()
public function test_exception_is_thrown_if_no_token()
{
- $this->expectException('Illuminate\Auth\AuthenticationException');
+ $this->expectException(AuthenticationException::class);
$middleware = new CheckScopes;
$request = m::mock();
diff --git a/tests/Unit/CheckScopesTest.php b/tests/Unit/CheckScopesTest.php
index 3eb172b9b..21e8b40bf 100644
--- a/tests/Unit/CheckScopesTest.php
+++ b/tests/Unit/CheckScopesTest.php
@@ -2,6 +2,7 @@
namespace Laravel\Passport\Tests\Unit;
+use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\Http\Middleware\CheckScopes;
use Mockery as m;
use PHPUnit\Framework\TestCase;
@@ -46,7 +47,7 @@ public function test_exception_is_thrown_if_token_doesnt_have_scope()
public function test_exception_is_thrown_if_no_authenticated_user()
{
- $this->expectException('Illuminate\Auth\AuthenticationException');
+ $this->expectException(AuthenticationException::class);
$middleware = new CheckScopes;
$request = m::mock();
@@ -59,7 +60,7 @@ public function test_exception_is_thrown_if_no_authenticated_user()
public function test_exception_is_thrown_if_no_token()
{
- $this->expectException('Illuminate\Auth\AuthenticationException');
+ $this->expectException(AuthenticationException::class);
$middleware = new CheckScopes;
$request = m::mock();
diff --git a/tests/Unit/DenyAuthorizationControllerTest.php b/tests/Unit/DenyAuthorizationControllerTest.php
index cfb5e01cc..e2cd26f69 100644
--- a/tests/Unit/DenyAuthorizationControllerTest.php
+++ b/tests/Unit/DenyAuthorizationControllerTest.php
@@ -2,12 +2,13 @@
namespace Laravel\Passport\Tests\Unit;
-use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Http\Request;
use Laravel\Passport\Http\Controllers\DenyAuthorizationController;
+use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use Mockery as m;
use PHPUnit\Framework\TestCase;
+use Psr\Http\Message\ResponseInterface;
class DenyAuthorizationControllerTest extends TestCase
{
@@ -18,130 +19,34 @@ protected function tearDown(): void
public function test_authorization_can_be_denied()
{
- $response = m::mock(ResponseFactory::class);
+ $this->expectException('Laravel\Passport\Exceptions\OAuthServerException');
- $controller = new DenyAuthorizationController($response);
+ $server = m::mock(AuthorizationServer::class);
+ $controller = new DenyAuthorizationController($server);
$request = m::mock(Request::class);
$request->shouldReceive('session')->andReturn($session = m::mock());
$request->shouldReceive('user')->andReturn(new DenyAuthorizationControllerFakeUser);
- $request->shouldReceive('input')->with('state')->andReturn('state');
$request->shouldReceive('has')->with('auth_token')->andReturn(true);
$request->shouldReceive('get')->with('auth_token')->andReturn('foo');
$session->shouldReceive('get')->once()->with('authToken')->andReturn('foo');
- $session->shouldReceive('get')->once()->with('authRequest')->andReturn($authRequest = m::mock(
- AuthorizationRequest::class
- ));
+ $session->shouldReceive('get')
+ ->once()
+ ->with('authRequest')
+ ->andReturn($authRequest = m::mock(
+ AuthorizationRequest::class
+ ));
$authRequest->shouldReceive('setUser')->once();
- $authRequest->shouldReceive('getGrantTypeId')->andReturn('authorization_code');
- $authRequest->shouldReceive('setAuthorizationApproved')->once()->with(true);
- $authRequest->shouldReceive('getRedirectUri')->andReturn('http://localhost');
- $authRequest->shouldReceive('getClient->getRedirectUri')->andReturn('http://localhost');
+ $authRequest->shouldReceive('setAuthorizationApproved')->once()->with(false);
- $response->shouldReceive('redirectTo')->once()->andReturnUsing(function ($url) {
- return $url;
- });
+ $server->shouldReceive('completeAuthorizationRequest')
+ ->with($authRequest, m::type(ResponseInterface::class))
+ ->andThrow('League\OAuth2\Server\Exception\OAuthServerException');
- $this->assertSame('http://localhost?error=access_denied&state=state', $controller->deny($request));
- }
-
- public function test_authorization_can_be_denied_with_multiple_redirect_uris()
- {
- $response = m::mock(ResponseFactory::class);
-
- $controller = new DenyAuthorizationController($response);
-
- $request = m::mock(Request::class);
-
- $request->shouldReceive('session')->andReturn($session = m::mock());
- $request->shouldReceive('user')->andReturn(new DenyAuthorizationControllerFakeUser);
- $request->shouldReceive('input')->with('state')->andReturn('state');
- $request->shouldReceive('has')->with('auth_token')->andReturn(true);
- $request->shouldReceive('get')->with('auth_token')->andReturn('foo');
-
- $session->shouldReceive('get')->once()->with('authRequest')->andReturn($authRequest = m::mock(
- AuthorizationRequest::class
- ));
-
- $authRequest->shouldReceive('setUser')->once();
- $authRequest->shouldReceive('getGrantTypeId')->andReturn('authorization_code');
- $authRequest->shouldReceive('setAuthorizationApproved')->once()->with(true);
- $authRequest->shouldReceive('getRedirectUri')->andReturn('http://localhost');
- $authRequest->shouldReceive('getClient->getRedirectUri')->andReturn(['http://localhost.localdomain', 'http://localhost']);
-
- $session->shouldReceive('get')->once()->with('authToken')->andReturn('foo');
- $response->shouldReceive('redirectTo')->once()->andReturnUsing(function ($url) {
- return $url;
- });
-
- $this->assertSame('http://localhost?error=access_denied&state=state', $controller->deny($request));
- }
-
- public function test_authorization_can_be_denied_implicit()
- {
- $response = m::mock(ResponseFactory::class);
-
- $controller = new DenyAuthorizationController($response);
-
- $request = m::mock(Request::class);
-
- $request->shouldReceive('session')->andReturn($session = m::mock());
- $request->shouldReceive('user')->andReturn(new DenyAuthorizationControllerFakeUser);
- $request->shouldReceive('input')->with('state')->andReturn('state');
- $request->shouldReceive('has')->with('auth_token')->andReturn(true);
- $request->shouldReceive('get')->with('auth_token')->andReturn('foo');
-
- $session->shouldReceive('get')->once()->with('authToken')->andReturn('foo');
- $session->shouldReceive('get')->once()->with('authRequest')->andReturn($authRequest = m::mock(
- AuthorizationRequest::class
- ));
-
- $authRequest->shouldReceive('setUser')->once();
- $authRequest->shouldReceive('getGrantTypeId')->andReturn('implicit');
- $authRequest->shouldReceive('setAuthorizationApproved')->once()->with(true);
- $authRequest->shouldReceive('getRedirectUri')->andReturn('http://localhost');
- $authRequest->shouldReceive('getClient->getRedirectUri')->andReturn('http://localhost');
-
- $response->shouldReceive('redirectTo')->once()->andReturnUsing(function ($url) {
- return $url;
- });
-
- $this->assertSame('http://localhost#error=access_denied&state=state', $controller->deny($request));
- }
-
- public function test_authorization_can_be_denied_with_existing_query_string()
- {
- $response = m::mock(ResponseFactory::class);
-
- $controller = new DenyAuthorizationController($response);
-
- $request = m::mock(Request::class);
-
- $request->shouldReceive('session')->andReturn($session = m::mock());
- $request->shouldReceive('user')->andReturn(new DenyAuthorizationControllerFakeUser);
- $request->shouldReceive('input')->with('state')->andReturn('state');
- $request->shouldReceive('has')->with('auth_token')->andReturn(true);
- $request->shouldReceive('get')->with('auth_token')->andReturn('foo');
-
- $session->shouldReceive('get')->once()->with('authToken')->andReturn('foo');
- $session->shouldReceive('get')->once()->with('authRequest')->andReturn($authRequest = m::mock(
- AuthorizationRequest::class
- ));
-
- $authRequest->shouldReceive('setUser')->once();
- $authRequest->shouldReceive('getGrantTypeId')->andReturn('authorization_code');
- $authRequest->shouldReceive('setAuthorizationApproved')->once()->with(true);
- $authRequest->shouldReceive('getRedirectUri')->andReturn('http://localhost?action=some_action');
- $authRequest->shouldReceive('getClient->getRedirectUri')->andReturn('http://localhost?action=some_action');
-
- $response->shouldReceive('redirectTo')->once()->andReturnUsing(function ($url) {
- return $url;
- });
-
- $this->assertSame('http://localhost?action=some_action&error=access_denied&state=state', $controller->deny($request));
+ $controller->deny($request);
}
public function test_auth_request_should_exist()
@@ -149,9 +54,9 @@ public function test_auth_request_should_exist()
$this->expectException('Exception');
$this->expectExceptionMessage('Authorization request was not present in the session.');
- $response = m::mock(ResponseFactory::class);
+ $server = m::mock(AuthorizationServer::class);
- $controller = new DenyAuthorizationController($response);
+ $controller = new DenyAuthorizationController($server);
$request = m::mock(Request::class);
@@ -164,7 +69,7 @@ public function test_auth_request_should_exist()
$session->shouldReceive('get')->once()->with('authToken')->andReturn('foo');
$session->shouldReceive('get')->once()->with('authRequest')->andReturnNull();
- $response->shouldReceive('redirectTo')->never();
+ $server->shouldReceive('completeAuthorizationRequest')->never();
$controller->deny($request);
}
diff --git a/tests/Unit/PersonalAccessTokenFactoryTest.php b/tests/Unit/PersonalAccessTokenFactoryTest.php
index f4731bd6b..c35127104 100644
--- a/tests/Unit/PersonalAccessTokenFactoryTest.php
+++ b/tests/Unit/PersonalAccessTokenFactoryTest.php
@@ -2,6 +2,7 @@
namespace Laravel\Passport\Tests\Unit;
+use Laravel\Passport\Client;
use Laravel\Passport\ClientRepository;
use Laravel\Passport\PersonalAccessTokenFactory;
use Laravel\Passport\PersonalAccessTokenResult;
@@ -41,7 +42,7 @@ public function test_access_token_can_be_created()
$parsedToken = new PlainToken(
new DataSet([], ''),
new DataSet([RegisteredClaims::ID => 'token'], ''),
- Signature::fromEmptyData()
+ new Signature('', '')
);
$jwt->shouldReceive('parse')->with('foo')->andReturn($parsedToken);
@@ -56,7 +57,7 @@ public function test_access_token_can_be_created()
}
}
-class PersonalAccessTokenFactoryTestClientStub
+class PersonalAccessTokenFactoryTestClientStub extends Client
{
public $id = 1;
diff --git a/tests/Unit/TokenGuardTest.php b/tests/Unit/TokenGuardTest.php
index 83608d861..614393898 100644
--- a/tests/Unit/TokenGuardTest.php
+++ b/tests/Unit/TokenGuardTest.php
@@ -37,11 +37,40 @@ public function test_user_can_be_pulled_via_bearer_token()
$clients = m::mock(ClientRepository::class);
$encrypter = m::mock(Encrypter::class);
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
+ $request = Request::create('/');
+ $request->headers->set('Authorization', 'Bearer token');
+
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
+ $resourceServer->shouldReceive('validateAuthenticatedRequest')->andReturn($psr = m::mock());
+ $psr->shouldReceive('getAttribute')->with('oauth_user_id')->andReturn(1);
+ $psr->shouldReceive('getAttribute')->with('oauth_client_id')->andReturn(1);
+ $psr->shouldReceive('getAttribute')->with('oauth_access_token_id')->andReturn('token');
+ $userProvider->shouldReceive('retrieveById')->with(1)->andReturn(new TokenGuardTestUser);
+ $userProvider->shouldReceive('getProviderName')->andReturn(null);
+ $tokens->shouldReceive('find')->once()->with('token')->andReturn($token = m::mock());
+ $clients->shouldReceive('revoked')->with(1)->andReturn(false);
+ $clients->shouldReceive('findActive')->with(1)->andReturn(new TokenGuardTestClient);
+
+ $user = $guard->user();
+
+ $this->assertInstanceOf(TokenGuardTestUser::class, $user);
+ $this->assertEquals($token, $user->token());
+ }
+
+ public function test_user_is_resolved_only_once()
+ {
+ $resourceServer = m::mock(ResourceServer::class);
+ $userProvider = m::mock(PassportUserProvider::class);
+ $tokens = m::mock(TokenRepository::class);
+ $clients = m::mock(ClientRepository::class);
+ $encrypter = m::mock(Encrypter::class);
$request = Request::create('/');
$request->headers->set('Authorization', 'Bearer token');
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$resourceServer->shouldReceive('validateAuthenticatedRequest')->andReturn($psr = m::mock());
$psr->shouldReceive('getAttribute')->with('oauth_user_id')->andReturn(1);
$psr->shouldReceive('getAttribute')->with('oauth_client_id')->andReturn(1);
@@ -52,10 +81,15 @@ public function test_user_can_be_pulled_via_bearer_token()
$clients->shouldReceive('revoked')->with(1)->andReturn(false);
$clients->shouldReceive('findActive')->with(1)->andReturn(new TokenGuardTestClient);
- $user = $guard->user($request);
+ $user = $guard->user();
+
+ $userProvider->shouldReceive('retrieveById')->never();
+
+ $user2 = $guard->user();
$this->assertInstanceOf(TokenGuardTestUser::class, $user);
$this->assertEquals($token, $user->token());
+ $this->assertSame($user, $user2);
}
public function test_no_user_is_returned_when_oauth_throws_exception()
@@ -71,19 +105,19 @@ public function test_no_user_is_returned_when_oauth_throws_exception()
$clients = m::mock(ClientRepository::class);
$encrypter = m::mock(Encrypter::class);
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
-
$request = Request::create('/');
$request->headers->set('Authorization', 'Bearer token');
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$resourceServer->shouldReceive('validateAuthenticatedRequest')->andThrow(
new OAuthServerException('message', 500, 'error type')
);
- $this->assertNull($guard->user($request));
+ $this->assertNull($guard->user());
// Assert that `validateAuthenticatedRequest` isn't called twice on failure.
- $this->assertNull($guard->user($request));
+ $this->assertNull($guard->user());
}
public function test_null_is_returned_if_no_user_is_found()
@@ -98,18 +132,18 @@ public function test_null_is_returned_if_no_user_is_found()
->with(1)
->andReturn(new TokenGuardTestClient);
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
-
$request = Request::create('/');
$request->headers->set('Authorization', 'Bearer token');
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$resourceServer->shouldReceive('validateAuthenticatedRequest')->andReturn($psr = m::mock());
$psr->shouldReceive('getAttribute')->with('oauth_user_id')->andReturn(1);
$psr->shouldReceive('getAttribute')->with('oauth_client_id')->andReturn(1);
$userProvider->shouldReceive('retrieveById')->with(1)->andReturn(null);
$userProvider->shouldReceive('getProviderName')->andReturn(null);
- $this->assertNull($guard->user($request));
+ $this->assertNull($guard->user());
}
public function test_users_may_be_retrieved_from_cookies_with_csrf_token_header()
@@ -124,8 +158,6 @@ public function test_users_may_be_retrieved_from_cookies_with_csrf_token_header(
->with(1)
->andReturn(new TokenGuardTestClient);
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
-
$request = Request::create('/');
$request->headers->set('X-CSRF-TOKEN', 'token');
$request->cookies->set('laravel_token',
@@ -137,10 +169,12 @@ public function test_users_may_be_retrieved_from_cookies_with_csrf_token_header(
], str_repeat('a', 16), 'HS256'), false)
);
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$userProvider->shouldReceive('retrieveById')->with(1)->andReturn($expectedUser = new TokenGuardTestUser);
$userProvider->shouldReceive('getProviderName')->andReturn(null);
- $user = $guard->user($request);
+ $user = $guard->user();
$this->assertEquals($expectedUser, $user);
}
@@ -157,8 +191,6 @@ public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header(
->with(1)
->andReturn(new TokenGuardTestClient);
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
-
$request = Request::create('/');
$request->headers->set('X-XSRF-TOKEN', $encrypter->encrypt(CookieValuePrefix::create('X-XSRF-TOKEN', $encrypter->getKey()).'token', false));
$request->cookies->set('laravel_token',
@@ -170,10 +202,12 @@ public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header(
], str_repeat('a', 16), 'HS256'), false)
);
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$userProvider->shouldReceive('retrieveById')->with(1)->andReturn($expectedUser = new TokenGuardTestUser);
$userProvider->shouldReceive('getProviderName')->andReturn(null);
- $user = $guard->user($request);
+ $user = $guard->user();
$this->assertEquals($expectedUser, $user);
}
@@ -186,8 +220,6 @@ public function test_cookie_xsrf_is_verified_against_csrf_token_header()
$clients = m::mock(ClientRepository::class);
$encrypter = new Encrypter(str_repeat('a', 16));
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
-
$request = Request::create('/');
$request->headers->set('X-CSRF-TOKEN', 'wrong_token');
$request->cookies->set('laravel_token',
@@ -199,9 +231,11 @@ public function test_cookie_xsrf_is_verified_against_csrf_token_header()
], str_repeat('a', 16), 'HS256'))
);
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$userProvider->shouldReceive('retrieveById')->never();
- $this->assertNull($guard->user($request));
+ $this->assertNull($guard->user());
}
public function test_cookie_xsrf_is_verified_against_xsrf_token_header()
@@ -212,8 +246,6 @@ public function test_cookie_xsrf_is_verified_against_xsrf_token_header()
$clients = m::mock(ClientRepository::class);
$encrypter = new Encrypter(str_repeat('a', 16));
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
-
$request = Request::create('/');
$request->headers->set('X-XSRF-TOKEN', $encrypter->encrypt('wrong_token', false));
$request->cookies->set('laravel_token',
@@ -225,9 +257,11 @@ public function test_cookie_xsrf_is_verified_against_xsrf_token_header()
], str_repeat('a', 16), 'HS256'))
);
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$userProvider->shouldReceive('retrieveById')->never();
- $this->assertNull($guard->user($request));
+ $this->assertNull($guard->user());
}
public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_when_using_a_custom_encryption_key()
@@ -246,8 +280,6 @@ public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_
->with(1)
->andReturn(new TokenGuardTestClient);
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
-
$request = Request::create('/');
$request->headers->set('X-XSRF-TOKEN', $encrypter->encrypt(CookieValuePrefix::create('X-XSRF-TOKEN', $encrypter->getKey()).'token', false));
$request->cookies->set('laravel_token',
@@ -259,10 +291,12 @@ public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_
], Passport::tokenEncryptionKey($encrypter), 'HS256'), false)
);
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$userProvider->shouldReceive('retrieveById')->with(1)->andReturn($expectedUser = new TokenGuardTestUser);
$userProvider->shouldReceive('getProviderName')->andReturn(null);
- $user = $guard->user($request);
+ $user = $guard->user();
$this->assertEquals($expectedUser, $user);
@@ -270,15 +304,55 @@ public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_
Passport::encryptTokensUsing(null);
}
- public function test_xsrf_token_cookie_without_a_token_header_is_not_accepted()
+ public function test_users_may_be_retrieved_from_cookies_without_encryption()
{
+ Passport::withoutCookieEncryption();
+ Passport::encryptTokensUsing(function (EncrypterContract $encrypter) {
+ return $encrypter->getKey().'.mykey';
+ });
+
$resourceServer = m::mock(ResourceServer::class);
$userProvider = m::mock(PassportUserProvider::class);
$tokens = m::mock(TokenRepository::class);
$clients = m::mock(ClientRepository::class);
$encrypter = new Encrypter(str_repeat('a', 16));
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
+ $clients->shouldReceive('findActive')
+ ->with(1)
+ ->andReturn(new TokenGuardTestClient);
+
+ $request = Request::create('/');
+ $request->headers->set('X-XSRF-TOKEN', $encrypter->encrypt(CookieValuePrefix::create('X-XSRF-TOKEN', $encrypter->getKey()).'token', false));
+ $request->cookies->set('laravel_token',
+ JWT::encode([
+ 'sub' => 1,
+ 'aud' => 1,
+ 'csrf' => 'token',
+ 'expiry' => Carbon::now()->addMinutes(10)->getTimestamp(),
+ ], Passport::tokenEncryptionKey($encrypter), 'HS256')
+ );
+
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
+ $userProvider->shouldReceive('retrieveById')->with(1)->andReturn($expectedUser = new TokenGuardTestUser);
+ $userProvider->shouldReceive('getProviderName')->andReturn(null);
+
+ $user = $guard->user();
+
+ $this->assertEquals($expectedUser, $user);
+
+ // Revert to the default encryption method
+ Passport::withCookieEncryption();
+ Passport::encryptTokensUsing(null);
+ }
+
+ public function test_xsrf_token_cookie_without_a_token_header_is_not_accepted()
+ {
+ $resourceServer = m::mock(ResourceServer::class);
+ $userProvider = m::mock(PassportUserProvider::class);
+ $tokens = m::mock(TokenRepository::class);
+ $clients = m::mock(ClientRepository::class);
+ $encrypter = new Encrypter(str_repeat('a', 16));
$request = Request::create('/');
$request->cookies->set('XSRF-TOKEN', $encrypter->encrypt('token', false));
@@ -291,9 +365,11 @@ public function test_xsrf_token_cookie_without_a_token_header_is_not_accepted()
], str_repeat('a', 16), 'HS256'))
);
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$userProvider->shouldReceive('retrieveById')->never();
- $this->assertNull($guard->user($request));
+ $this->assertNull($guard->user());
}
public function test_expired_cookies_may_not_be_used()
@@ -304,8 +380,6 @@ public function test_expired_cookies_may_not_be_used()
$clients = m::mock(ClientRepository::class);
$encrypter = new Encrypter(str_repeat('a', 16));
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
-
$request = Request::create('/');
$request->headers->set('X-CSRF-TOKEN', 'token');
$request->cookies->set('laravel_token',
@@ -317,9 +391,11 @@ public function test_expired_cookies_may_not_be_used()
], str_repeat('a', 16), 'HS256'))
);
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$userProvider->shouldReceive('retrieveById')->never();
- $this->assertNull($guard->user($request));
+ $this->assertNull($guard->user());
}
public function test_csrf_check_can_be_disabled()
@@ -334,8 +410,6 @@ public function test_csrf_check_can_be_disabled()
->with(1)
->andReturn(new TokenGuardTestClient);
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
-
Passport::ignoreCsrfToken();
$request = Request::create('/');
@@ -347,10 +421,12 @@ public function test_csrf_check_can_be_disabled()
], str_repeat('a', 16), 'HS256'), false)
);
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$userProvider->shouldReceive('retrieveById')->with(1)->andReturn($expectedUser = new TokenGuardTestUser);
$userProvider->shouldReceive('getProviderName')->andReturn(null);
- $user = $guard->user($request);
+ $user = $guard->user();
$this->assertEquals($expectedUser, $user);
}
@@ -363,18 +439,45 @@ public function test_client_can_be_pulled_via_bearer_token()
$clients = m::mock(ClientRepository::class);
$encrypter = m::mock(Encrypter::class);
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
+ $request = Request::create('/');
+ $request->headers->set('Authorization', 'Bearer token');
+
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
+ $resourceServer->shouldReceive('validateAuthenticatedRequest')->andReturn($psr = m::mock());
+ $psr->shouldReceive('getAttribute')->with('oauth_client_id')->andReturn(1);
+ $clients->shouldReceive('findActive')->with(1)->andReturn(new TokenGuardTestClient);
+
+ $client = $guard->client();
+
+ $this->assertInstanceOf(TokenGuardTestClient::class, $client);
+ }
+
+ public function test_client_is_resolved_only_once()
+ {
+ $resourceServer = m::mock(ResourceServer::class);
+ $userProvider = m::mock(PassportUserProvider::class);
+ $tokens = m::mock(TokenRepository::class);
+ $clients = m::mock(ClientRepository::class);
+ $encrypter = m::mock(Encrypter::class);
$request = Request::create('/');
$request->headers->set('Authorization', 'Bearer token');
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$resourceServer->shouldReceive('validateAuthenticatedRequest')->andReturn($psr = m::mock());
$psr->shouldReceive('getAttribute')->with('oauth_client_id')->andReturn(1);
$clients->shouldReceive('findActive')->with(1)->andReturn(new TokenGuardTestClient);
- $client = $guard->client($request);
+ $client = $guard->client();
+
+ $clients->shouldReceive('findActive')->never();
+
+ $client2 = $guard->client();
$this->assertInstanceOf(TokenGuardTestClient::class, $client);
+ $this->assertSame($client, $client2);
}
public function test_no_client_is_returned_when_oauth_throws_exception()
@@ -390,19 +493,19 @@ public function test_no_client_is_returned_when_oauth_throws_exception()
$clients = m::mock(ClientRepository::class);
$encrypter = m::mock(Encrypter::class);
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
-
$request = Request::create('/');
$request->headers->set('Authorization', 'Bearer token');
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$resourceServer->shouldReceive('validateAuthenticatedRequest')->andThrow(
new OAuthServerException('message', 500, 'error type')
);
- $this->assertNull($guard->client($request));
+ $this->assertNull($guard->client());
// Assert that `validateAuthenticatedRequest` isn't called twice on failure.
- $this->assertNull($guard->client($request));
+ $this->assertNull($guard->client());
}
public function test_null_is_returned_if_no_client_is_found()
@@ -413,16 +516,16 @@ public function test_null_is_returned_if_no_client_is_found()
$clients = m::mock(ClientRepository::class);
$encrypter = m::mock(Encrypter::class);
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
-
$request = Request::create('/');
$request->headers->set('Authorization', 'Bearer token');
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$resourceServer->shouldReceive('validateAuthenticatedRequest')->andReturn($psr = m::mock());
$psr->shouldReceive('getAttribute')->with('oauth_client_id')->andReturn(1);
$clients->shouldReceive('findActive')->with(1)->andReturn(null);
- $this->assertNull($guard->client($request));
+ $this->assertNull($guard->client());
}
public function test_clients_may_be_retrieved_from_cookies()
@@ -433,8 +536,6 @@ public function test_clients_may_be_retrieved_from_cookies()
$clients = m::mock(ClientRepository::class);
$encrypter = new Encrypter(str_repeat('a', 16));
- $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
-
$request = Request::create('/');
$request->headers->set('X-CSRF-TOKEN', 'token');
$request->cookies->set('laravel_token',
@@ -446,9 +547,11 @@ public function test_clients_may_be_retrieved_from_cookies()
], str_repeat('a', 16), 'HS256'), false)
);
+ $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request);
+
$clients->shouldReceive('findActive')->with(1)->andReturn($expectedClient = new TokenGuardTestClient);
- $client = $guard->client($request);
+ $client = $guard->client();
$this->assertEquals($expectedClient, $client);
}
diff --git a/workbench/app/Models/User.php b/workbench/app/Models/User.php
new file mode 100644
index 000000000..6987bfb38
--- /dev/null
+++ b/workbench/app/Models/User.php
@@ -0,0 +1,42 @@
+
+ */
+ protected $fillable = [
+ 'name',
+ 'email',
+ 'password',
+ ];
+
+ /**
+ * The attributes that should be hidden for serialization.
+ *
+ * @var array
+ */
+ protected $hidden = [
+ 'password',
+ 'remember_token',
+ ];
+
+ /**
+ * The attributes that should be cast.
+ *
+ * @var array
+ */
+ protected $casts = [
+ 'email_verified_at' => 'datetime',
+ ];
+}
diff --git a/workbench/database/factories/UserFactory.php b/workbench/database/factories/UserFactory.php
new file mode 100644
index 000000000..db6dfa3df
--- /dev/null
+++ b/workbench/database/factories/UserFactory.php
@@ -0,0 +1,20 @@
+
+ */
+class UserFactory extends \Orchestra\Testbench\Factories\UserFactory
+{
+ /**
+ * The name of the factory's corresponding model.
+ *
+ * @var class-string<\TModel>
+ */
+ protected $model = User::class;
+}