From 9b7ba7e136c5365a44d04594e54cab1b43c4e1ce Mon Sep 17 00:00:00 2001 From: Thijs Kok Date: Thu, 28 Oct 2021 14:57:53 +0200 Subject: [PATCH 1/4] Added Passport custom encryption key --- src/ApiTokenCookieFactory.php | 2 +- src/Guards/TokenGuard.php | 2 +- src/Passport.php | 34 ++++++++++++++++++++ tests/Unit/ApiTokenCookieFactoryTest.php | 27 ++++++++++++++++ tests/Unit/TokenGuardTest.php | 41 ++++++++++++++++++++++++ 5 files changed, 104 insertions(+), 2 deletions(-) diff --git a/src/ApiTokenCookieFactory.php b/src/ApiTokenCookieFactory.php index 23a6cd989..d7506480f 100644 --- a/src/ApiTokenCookieFactory.php +++ b/src/ApiTokenCookieFactory.php @@ -77,6 +77,6 @@ protected function createToken($userId, $csrfToken, Carbon $expiration) 'sub' => $userId, 'csrf' => $csrfToken, 'expiry' => $expiration->getTimestamp(), - ], $this->encrypter->getKey()); + ], Passport::encryptionKey($this->encrypter)); } } diff --git a/src/Guards/TokenGuard.php b/src/Guards/TokenGuard.php index 7d81ed068..298571b0c 100644 --- a/src/Guards/TokenGuard.php +++ b/src/Guards/TokenGuard.php @@ -269,7 +269,7 @@ protected function decodeJwtTokenCookie($request) { return (array) JWT::decode( CookieValuePrefix::remove($this->encrypter->decrypt($request->cookie(Passport::cookie()), Passport::$unserializesCookies)), - $this->encrypter->getKey(), + Passport::encryptionKey($this->encrypter), ['HS256'] ); } diff --git a/src/Passport.php b/src/Passport.php index 675e43dff..dab6df24d 100644 --- a/src/Passport.php +++ b/src/Passport.php @@ -9,6 +9,7 @@ use League\OAuth2\Server\ResourceServer; use Mockery; use Psr\Http\Message\ServerRequestInterface; +use Illuminate\Contracts\Encryption\Encrypter; class Passport { @@ -138,6 +139,11 @@ class Passport */ public static $hashesClientSecrets = false; + /** + * @var callable + */ + public static $encryptsKeys; + /** * Indicates the scope should inherit its parent scope. * @@ -616,6 +622,34 @@ public static function hashClientSecrets() return new static; } + /** + * Make Passport use your own encryption key when encrypting tokens. + * + * @param callable $callback + * + * @return static + */ + public static function encryptUsing($callback) + { + static::$encryptsKeys = $callback; + + return new static; + } + + /** + * Generates a token encryption key. + * + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * + * @return string + */ + public static function encryptionKey(Encrypter $encrypter) + { + return is_callable(static::$encryptsKeys) ? + (static::$encryptsKeys)($encrypter) : + $encrypter->getKey(); + } + /** * Configure Passport to not register its migrations. * diff --git a/tests/Unit/ApiTokenCookieFactoryTest.php b/tests/Unit/ApiTokenCookieFactoryTest.php index 5451bcb1d..1beede9e4 100644 --- a/tests/Unit/ApiTokenCookieFactoryTest.php +++ b/tests/Unit/ApiTokenCookieFactoryTest.php @@ -2,12 +2,14 @@ namespace Laravel\Passport\Tests\Unit; +use Laravel\Passport\Passport; use Illuminate\Contracts\Config\Repository; use Illuminate\Encryption\Encrypter; use Laravel\Passport\ApiTokenCookieFactory; use Mockery as m; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Cookie; +use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract; class ApiTokenCookieFactoryTest extends TestCase { @@ -33,4 +35,29 @@ public function test_cookie_can_be_successfully_created() $this->assertInstanceOf(Cookie::class, $cookie); } + + public function test_cookie_can_be_successfully_created_when_using_a_custom_encryption_key() + { + Passport::encryptUsing(function(EncrypterContract $encrypter) { + return $encrypter->getKey() . '.mykey'; + }); + + $config = m::mock(Repository::class); + $config->shouldReceive('get')->with('session')->andReturn([ + 'lifetime' => 120, + 'path' => '/', + 'domain' => null, + 'secure' => true, + 'same_site' => 'lax', + ]); + $encrypter = new Encrypter(str_repeat('a', 16)); + $factory = new ApiTokenCookieFactory($config, $encrypter); + + $cookie = $factory->make(1, 'token'); + + $this->assertInstanceOf(Cookie::class, $cookie); + + // Revert to the default encryption method + Passport::encryptUsing(null); + } } diff --git a/tests/Unit/TokenGuardTest.php b/tests/Unit/TokenGuardTest.php index a7f4b8556..0d830db40 100644 --- a/tests/Unit/TokenGuardTest.php +++ b/tests/Unit/TokenGuardTest.php @@ -19,6 +19,7 @@ use League\OAuth2\Server\ResourceServer; use Mockery as m; use PHPUnit\Framework\TestCase; +use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract; class TokenGuardTest extends TestCase { @@ -229,6 +230,46 @@ public function test_cookie_xsrf_is_verified_against_xsrf_token_header() $this->assertNull($guard->user($request)); } + public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_when_using_a_custom_encryption_key() + { + Passport::encryptUsing(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)); + + $clients->shouldReceive('findActive') + ->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', + $encrypter->encrypt(CookieValuePrefix::create('laravel_token', $encrypter->getKey()).JWT::encode([ + 'sub' => 1, + 'aud' => 1, + 'csrf' => 'token', + 'expiry' => Carbon::now()->addMinutes(10)->getTimestamp(), + ], Passport::encryptionKey($encrypter)), false) + ); + + $userProvider->shouldReceive('retrieveById')->with(1)->andReturn($expectedUser = new TokenGuardTestUser); + $userProvider->shouldReceive('getProviderName')->andReturn(null); + + $user = $guard->user($request); + + $this->assertEquals($expectedUser, $user); + + // Revert to the default encryption method + Passport::encryptUsing(null); + } + public function test_xsrf_token_cookie_without_a_token_header_is_not_accepted() { $resourceServer = m::mock(ResourceServer::class); From a80365399132bcea7f2fb56d6268d4e569dda7d4 Mon Sep 17 00:00:00 2001 From: Thijs Kok Date: Thu, 28 Oct 2021 17:30:11 +0200 Subject: [PATCH 2/4] Refactored encryptUsing method --- src/Passport.php | 2 +- tests/Unit/ApiTokenCookieFactoryTest.php | 4 ++-- tests/Unit/TokenGuardTest.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Passport.php b/src/Passport.php index dab6df24d..254a1461e 100644 --- a/src/Passport.php +++ b/src/Passport.php @@ -629,7 +629,7 @@ public static function hashClientSecrets() * * @return static */ - public static function encryptUsing($callback) + public static function encryptTokenUsing($callback) { static::$encryptsKeys = $callback; diff --git a/tests/Unit/ApiTokenCookieFactoryTest.php b/tests/Unit/ApiTokenCookieFactoryTest.php index 1beede9e4..60409ea2b 100644 --- a/tests/Unit/ApiTokenCookieFactoryTest.php +++ b/tests/Unit/ApiTokenCookieFactoryTest.php @@ -38,7 +38,7 @@ public function test_cookie_can_be_successfully_created() public function test_cookie_can_be_successfully_created_when_using_a_custom_encryption_key() { - Passport::encryptUsing(function(EncrypterContract $encrypter) { + Passport::encryptTokenUsing(function(EncrypterContract $encrypter) { return $encrypter->getKey() . '.mykey'; }); @@ -58,6 +58,6 @@ public function test_cookie_can_be_successfully_created_when_using_a_custom_encr $this->assertInstanceOf(Cookie::class, $cookie); // Revert to the default encryption method - Passport::encryptUsing(null); + Passport::encryptTokenUsing(null); } } diff --git a/tests/Unit/TokenGuardTest.php b/tests/Unit/TokenGuardTest.php index 0d830db40..8abc06a39 100644 --- a/tests/Unit/TokenGuardTest.php +++ b/tests/Unit/TokenGuardTest.php @@ -232,7 +232,7 @@ public function test_cookie_xsrf_is_verified_against_xsrf_token_header() public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_when_using_a_custom_encryption_key() { - Passport::encryptUsing(function(EncrypterContract $encrypter) { + Passport::encryptTokenUsing(function(EncrypterContract $encrypter) { return $encrypter->getKey() . '.mykey'; }); @@ -267,7 +267,7 @@ public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_ $this->assertEquals($expectedUser, $user); // Revert to the default encryption method - Passport::encryptUsing(null); + Passport::encryptTokenUsing(null); } public function test_xsrf_token_cookie_without_a_token_header_is_not_accepted() From 2f8d277c572cd0a71ba3bc8ce51da93c44818540 Mon Sep 17 00:00:00 2001 From: Thijs Kok Date: Thu, 28 Oct 2021 20:15:09 +0200 Subject: [PATCH 3/4] StyleCI fixes --- src/Passport.php | 8 +++----- tests/Unit/ApiTokenCookieFactoryTest.php | 8 ++++---- tests/Unit/TokenGuardTest.php | 16 ++++++++-------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/Passport.php b/src/Passport.php index 254a1461e..43980ae3a 100644 --- a/src/Passport.php +++ b/src/Passport.php @@ -5,11 +5,11 @@ use Carbon\Carbon; use DateInterval; use DateTimeInterface; +use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Support\Facades\Route; use League\OAuth2\Server\ResourceServer; use Mockery; use Psr\Http\Message\ServerRequestInterface; -use Illuminate\Contracts\Encryption\Encrypter; class Passport { @@ -625,8 +625,7 @@ public static function hashClientSecrets() /** * Make Passport use your own encryption key when encrypting tokens. * - * @param callable $callback - * + * @param callable $callback * @return static */ public static function encryptTokenUsing($callback) @@ -639,8 +638,7 @@ public static function encryptTokenUsing($callback) /** * Generates a token encryption key. * - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter - * + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter * @return string */ public static function encryptionKey(Encrypter $encrypter) diff --git a/tests/Unit/ApiTokenCookieFactoryTest.php b/tests/Unit/ApiTokenCookieFactoryTest.php index 60409ea2b..9c7a8eefc 100644 --- a/tests/Unit/ApiTokenCookieFactoryTest.php +++ b/tests/Unit/ApiTokenCookieFactoryTest.php @@ -2,14 +2,14 @@ namespace Laravel\Passport\Tests\Unit; -use Laravel\Passport\Passport; use Illuminate\Contracts\Config\Repository; +use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract; use Illuminate\Encryption\Encrypter; use Laravel\Passport\ApiTokenCookieFactory; +use Laravel\Passport\Passport; use Mockery as m; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Cookie; -use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract; class ApiTokenCookieFactoryTest extends TestCase { @@ -38,8 +38,8 @@ public function test_cookie_can_be_successfully_created() public function test_cookie_can_be_successfully_created_when_using_a_custom_encryption_key() { - Passport::encryptTokenUsing(function(EncrypterContract $encrypter) { - return $encrypter->getKey() . '.mykey'; + Passport::encryptTokenUsing(function (EncrypterContract $encrypter) { + return $encrypter->getKey().'.mykey'; }); $config = m::mock(Repository::class); diff --git a/tests/Unit/TokenGuardTest.php b/tests/Unit/TokenGuardTest.php index 8abc06a39..8ea4eda21 100644 --- a/tests/Unit/TokenGuardTest.php +++ b/tests/Unit/TokenGuardTest.php @@ -6,6 +6,7 @@ use Firebase\JWT\JWT; use Illuminate\Container\Container; use Illuminate\Contracts\Debug\ExceptionHandler; +use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract; use Illuminate\Cookie\CookieValuePrefix; use Illuminate\Encryption\Encrypter; use Illuminate\Http\Request; @@ -19,7 +20,6 @@ use League\OAuth2\Server\ResourceServer; use Mockery as m; use PHPUnit\Framework\TestCase; -use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract; class TokenGuardTest extends TestCase { @@ -232,8 +232,8 @@ public function test_cookie_xsrf_is_verified_against_xsrf_token_header() public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_when_using_a_custom_encryption_key() { - Passport::encryptTokenUsing(function(EncrypterContract $encrypter) { - return $encrypter->getKey() . '.mykey'; + Passport::encryptTokenUsing(function (EncrypterContract $encrypter) { + return $encrypter->getKey().'.mykey'; }); $resourceServer = m::mock(ResourceServer::class); @@ -252,11 +252,11 @@ public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_ $request->headers->set('X-XSRF-TOKEN', $encrypter->encrypt(CookieValuePrefix::create('X-XSRF-TOKEN', $encrypter->getKey()).'token', false)); $request->cookies->set('laravel_token', $encrypter->encrypt(CookieValuePrefix::create('laravel_token', $encrypter->getKey()).JWT::encode([ - 'sub' => 1, - 'aud' => 1, - 'csrf' => 'token', - 'expiry' => Carbon::now()->addMinutes(10)->getTimestamp(), - ], Passport::encryptionKey($encrypter)), false) + 'sub' => 1, + 'aud' => 1, + 'csrf' => 'token', + 'expiry' => Carbon::now()->addMinutes(10)->getTimestamp(), + ], Passport::encryptionKey($encrypter)), false) ); $userProvider->shouldReceive('retrieveById')->with(1)->andReturn($expectedUser = new TokenGuardTestUser); From fef44ba1001a867faca0ff29a4c9058c8338e303 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Fri, 29 Oct 2021 08:59:22 -0500 Subject: [PATCH 4/4] formatting --- src/ApiTokenCookieFactory.php | 2 +- src/Guards/TokenGuard.php | 2 +- src/Passport.php | 20 ++++++++++++-------- tests/Unit/ApiTokenCookieFactoryTest.php | 4 ++-- tests/Unit/TokenGuardTest.php | 6 +++--- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/ApiTokenCookieFactory.php b/src/ApiTokenCookieFactory.php index d7506480f..3d7d83d02 100644 --- a/src/ApiTokenCookieFactory.php +++ b/src/ApiTokenCookieFactory.php @@ -77,6 +77,6 @@ protected function createToken($userId, $csrfToken, Carbon $expiration) 'sub' => $userId, 'csrf' => $csrfToken, 'expiry' => $expiration->getTimestamp(), - ], Passport::encryptionKey($this->encrypter)); + ], Passport::tokenEncryptionKey($this->encrypter)); } } diff --git a/src/Guards/TokenGuard.php b/src/Guards/TokenGuard.php index 298571b0c..00cbe4863 100644 --- a/src/Guards/TokenGuard.php +++ b/src/Guards/TokenGuard.php @@ -269,7 +269,7 @@ protected function decodeJwtTokenCookie($request) { return (array) JWT::decode( CookieValuePrefix::remove($this->encrypter->decrypt($request->cookie(Passport::cookie()), Passport::$unserializesCookies)), - Passport::encryptionKey($this->encrypter), + Passport::tokenEncryptionKey($this->encrypter), ['HS256'] ); } diff --git a/src/Passport.php b/src/Passport.php index 43980ae3a..adf4c53ca 100644 --- a/src/Passport.php +++ b/src/Passport.php @@ -135,14 +135,18 @@ class Passport public static $unserializesCookies = false; /** + * Indicates if client secrets will be hashed. + * * @var bool */ public static $hashesClientSecrets = false; /** + * The callback that should be used to generate JWT encryption keys. + * * @var callable */ - public static $encryptsKeys; + public static $tokenEncryptionKeyCallback; /** * Indicates the scope should inherit its parent scope. @@ -623,28 +627,28 @@ public static function hashClientSecrets() } /** - * Make Passport use your own encryption key when encrypting tokens. + * Specify the callback that should be invoked to generate encryption keys for encrypting JWT tokens. * * @param callable $callback * @return static */ - public static function encryptTokenUsing($callback) + public static function encryptTokensUsing($callback) { - static::$encryptsKeys = $callback; + static::$tokenEncryptionKeyCallback = $callback; return new static; } /** - * Generates a token encryption key. + * Generate an encryption key for encrypting JWT tokens. * * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter * @return string */ - public static function encryptionKey(Encrypter $encrypter) + public static function tokenEncryptionKey(Encrypter $encrypter) { - return is_callable(static::$encryptsKeys) ? - (static::$encryptsKeys)($encrypter) : + return is_callable(static::$tokenEncryptionKeyCallback) ? + (static::$tokenEncryptionKeyCallback)($encrypter) : $encrypter->getKey(); } diff --git a/tests/Unit/ApiTokenCookieFactoryTest.php b/tests/Unit/ApiTokenCookieFactoryTest.php index 9c7a8eefc..1a82bbfbf 100644 --- a/tests/Unit/ApiTokenCookieFactoryTest.php +++ b/tests/Unit/ApiTokenCookieFactoryTest.php @@ -38,7 +38,7 @@ public function test_cookie_can_be_successfully_created() public function test_cookie_can_be_successfully_created_when_using_a_custom_encryption_key() { - Passport::encryptTokenUsing(function (EncrypterContract $encrypter) { + Passport::encryptTokensUsing(function (EncrypterContract $encrypter) { return $encrypter->getKey().'.mykey'; }); @@ -58,6 +58,6 @@ public function test_cookie_can_be_successfully_created_when_using_a_custom_encr $this->assertInstanceOf(Cookie::class, $cookie); // Revert to the default encryption method - Passport::encryptTokenUsing(null); + Passport::encryptTokensUsing(null); } } diff --git a/tests/Unit/TokenGuardTest.php b/tests/Unit/TokenGuardTest.php index 8ea4eda21..b586eff99 100644 --- a/tests/Unit/TokenGuardTest.php +++ b/tests/Unit/TokenGuardTest.php @@ -232,7 +232,7 @@ public function test_cookie_xsrf_is_verified_against_xsrf_token_header() public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_when_using_a_custom_encryption_key() { - Passport::encryptTokenUsing(function (EncrypterContract $encrypter) { + Passport::encryptTokensUsing(function (EncrypterContract $encrypter) { return $encrypter->getKey().'.mykey'; }); @@ -256,7 +256,7 @@ public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_ 'aud' => 1, 'csrf' => 'token', 'expiry' => Carbon::now()->addMinutes(10)->getTimestamp(), - ], Passport::encryptionKey($encrypter)), false) + ], Passport::tokenEncryptionKey($encrypter)), false) ); $userProvider->shouldReceive('retrieveById')->with(1)->andReturn($expectedUser = new TokenGuardTestUser); @@ -267,7 +267,7 @@ public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_ $this->assertEquals($expectedUser, $user); // Revert to the default encryption method - Passport::encryptTokenUsing(null); + Passport::encryptTokensUsing(null); } public function test_xsrf_token_cookie_without_a_token_header_is_not_accepted()