Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.0] Add the ability to retrieve current client #854

Merged
merged 1 commit into from
Oct 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 87 additions & 36 deletions src/Guards/TokenGuard.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,29 @@ public function user(Request $request)
}
}

/**
* Get the client for the incoming request.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function client(Request $request)
{
if ($request->bearerToken()) {
if (! $psr = $this->getPsrRequestViaBearerToken($request)) {
return;
}

return $this->clients->findActive(
$psr->getAttribute('oauth_client_id')
);
} elseif ($request->cookie(Passport::cookie())) {
if ($token = $this->getTokenViaCookie($request)) {
return $this->clients->findActive($token['aud']);
}
}
}

/**
* Authenticate the incoming request via the Bearer token.
*
Expand All @@ -100,44 +123,57 @@ public function user(Request $request)
*/
protected function authenticateViaBearerToken($request)
{
// First, we will convert the Symfony request to a PSR-7 implementation which will
// be compatible with the base OAuth2 library. The Symfony bridge can perform a
// conversion for us to a Zend Diactoros implementation of the PSR-7 request.
$psr = (new DiactorosFactory)->createRequest($request);
if (! $psr = $this->getPsrRequestViaBearerToken($request)) {
return;
}

try {
$psr = $this->server->validateAuthenticatedRequest($psr);
// If the access token is valid we will retrieve the user according to the user ID
// associated with the token. We will use the provider implementation which may
// be used to retrieve users from Eloquent. Next, we'll be ready to continue.
$user = $this->provider->retrieveById(
$psr->getAttribute('oauth_user_id')
);

// If the access token is valid we will retrieve the user according to the user ID
// associated with the token. We will use the provider implementation which may
// be used to retrieve users from Eloquent. Next, we'll be ready to continue.
$user = $this->provider->retrieveById(
$psr->getAttribute('oauth_user_id')
);
if (! $user) {
return;
}

if (! $user) {
return;
}
// Next, we will assign a token instance to this user which the developers may use
// to determine if the token has a given scope, etc. This will be useful during
// authorization such as within the developer's Laravel model policy classes.
$token = $this->tokens->find(
$psr->getAttribute('oauth_access_token_id')
);

// Next, we will assign a token instance to this user which the developers may use
// to determine if the token has a given scope, etc. This will be useful during
// authorization such as within the developer's Laravel model policy classes.
$token = $this->tokens->find(
$psr->getAttribute('oauth_access_token_id')
);
$clientId = $psr->getAttribute('oauth_client_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;
}

// 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;
}

/**
* Authenticate and get the incoming PSR-7 request via the Bearer token.
*
* @param \Illuminate\Http\Request $request
* @return \Psr\Http\Message\ServerRequestInterface
*/
protected function getPsrRequestViaBearerToken($request)
{
// First, we will convert the Symfony request to a PSR-7 implementation which will
// be compatible with the base OAuth2 library. The Symfony bridge can perform a
// conversion for us to a Zend Diactoros implementation of the PSR-7 request.
$psr = (new DiactorosFactory)->createRequest($request);

return $token ? $user->withAccessToken($token) : null;
try {
return $this->server->validateAuthenticatedRequest($psr);
} catch (OAuthServerException $e) {
$request->headers->set( 'Authorization', '', true );
$request->headers->set('Authorization', '', true);

Container::getInstance()->make(
ExceptionHandler::class
Expand All @@ -152,6 +188,26 @@ protected function authenticateViaBearerToken($request)
* @return mixed
*/
protected function authenticateViaCookie($request)
{
if (! $token = $this->getTokenViaCookie($request)) {
return;
}

// If this user exists, we will return this user and attach a "transient" token to
// the user model. The transient token assumes it has all scopes since the user
// is physically logged into the application via the application's interface.
if ($user = $this->provider->retrieveById($token['sub'])) {
return $user->withAccessToken(new TransientToken);
}
}

/**
* Get the token cookie via the incoming request.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function getTokenViaCookie($request)
{
// If we need to retrieve the token from the cookie, it'll be encrypted so we must
// first decrypt the cookie and then attempt to find the token value within the
Expand All @@ -170,12 +226,7 @@ protected function authenticateViaCookie($request)
return;
}

// If this user exists, we will return this user and attach a "transient" token to
// the user model. The transient token assumes it has all scopes since the user
// is physically logged into the application via the application's interface.
if ($user = $this->provider->retrieveById($token['sub'])) {
return $user->withAccessToken(new TransientToken);
}
return $token;
}

/**
Expand Down
115 changes: 112 additions & 3 deletions tests/TokenGuardTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ public function test_users_may_be_retrieved_from_cookies()
$request->headers->set('X-CSRF-TOKEN', 'token');
$request->cookies->set('laravel_token',
$encrypter->encrypt(JWT::encode([
'sub' => 1, 'csrf' => 'token',
'sub' => 1,
'aud' => 1,
'csrf' => 'token',
'expiry' => Carbon::now()->addMinutes(10)->getTimestamp(),
], str_repeat('a', 16)), false)
);
Expand All @@ -130,7 +132,9 @@ public function test_cookie_xsrf_is_verified_against_header()
$request->headers->set('X-CSRF-TOKEN', 'wrong_token');
$request->cookies->set('laravel_token',
$encrypter->encrypt(JWT::encode([
'sub' => 1, 'csrf' => 'token',
'sub' => 1,
'aud' => 1,
'csrf' => 'token',
'expiry' => Carbon::now()->addMinutes(10)->getTimestamp(),
], str_repeat('a', 16)))
);
Expand All @@ -154,7 +158,9 @@ public function test_expired_cookies_may_not_be_used()
$request->headers->set('X-CSRF-TOKEN', 'token');
$request->cookies->set('laravel_token',
$encrypter->encrypt(JWT::encode([
'sub' => 1, 'csrf' => 'token',
'sub' => 1,
'aud' => 1,
'csrf' => 'token',
'expiry' => Carbon::now()->subMinutes(10)->getTimestamp(),
], str_repeat('a', 16)))
);
Expand All @@ -180,6 +186,7 @@ public function test_csrf_check_can_be_disabled()
$request->cookies->set('laravel_token',
$encrypter->encrypt(JWT::encode([
'sub' => 1,
'aud' => 1,
'expiry' => Carbon::now()->addMinutes(10)->getTimestamp(),
], str_repeat('a', 16)), false)
);
Expand All @@ -190,9 +197,111 @@ public function test_csrf_check_can_be_disabled()

$this->assertEquals($expectedUser, $user);
}

public function test_client_can_be_pulled_via_bearer_token()
{
$resourceServer = Mockery::mock('League\OAuth2\Server\ResourceServer');
$userProvider = Mockery::mock('Illuminate\Contracts\Auth\UserProvider');
$tokens = Mockery::mock('Laravel\Passport\TokenRepository');
$clients = Mockery::mock('Laravel\Passport\ClientRepository');
$encrypter = Mockery::mock('Illuminate\Contracts\Encryption\Encrypter');

$guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);

$request = Request::create('/');
$request->headers->set('Authorization', 'Bearer token');

$resourceServer->shouldReceive('validateAuthenticatedRequest')->andReturn($psr = Mockery::mock());
$psr->shouldReceive('getAttribute')->with('oauth_client_id')->andReturn(1);
$clients->shouldReceive('findActive')->with(1)->andReturn(new TokenGuardTestClient);

$client = $guard->client($request);

$this->assertInstanceOf('TokenGuardTestClient', $client);
}

public function test_no_client_is_returned_when_oauth_throws_exception()
{
$container = new Container;
Container::setInstance($container);
$container->instance('Illuminate\Contracts\Debug\ExceptionHandler', $handler = Mockery::mock());
$handler->shouldReceive('report')->once()->with(Mockery::type('League\OAuth2\Server\Exception\OAuthServerException'));

$resourceServer = Mockery::mock('League\OAuth2\Server\ResourceServer');
$userProvider = Mockery::mock('Illuminate\Contracts\Auth\UserProvider');
$tokens = Mockery::mock('Laravel\Passport\TokenRepository');
$clients = Mockery::mock('Laravel\Passport\ClientRepository');
$encrypter = Mockery::mock('Illuminate\Contracts\Encryption\Encrypter');

$guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);

$request = Request::create('/');
$request->headers->set('Authorization', 'Bearer token');

$resourceServer->shouldReceive('validateAuthenticatedRequest')->andThrow(
new League\OAuth2\Server\Exception\OAuthServerException('message', 500, 'error type')
);

$this->assertNull($guard->client($request));

// Assert that `validateAuthenticatedRequest` isn't called twice on failure.
$this->assertNull($guard->client($request));
}

public function test_null_is_returned_if_no_client_is_found()
{
$resourceServer = Mockery::mock('League\OAuth2\Server\ResourceServer');
$userProvider = Mockery::mock('Illuminate\Contracts\Auth\UserProvider');
$tokens = Mockery::mock('Laravel\Passport\TokenRepository');
$clients = Mockery::mock('Laravel\Passport\ClientRepository');
$encrypter = Mockery::mock('Illuminate\Contracts\Encryption\Encrypter');

$guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);

$request = Request::create('/');
$request->headers->set('Authorization', 'Bearer token');

$resourceServer->shouldReceive('validateAuthenticatedRequest')->andReturn($psr = Mockery::mock());
$psr->shouldReceive('getAttribute')->with('oauth_client_id')->andReturn(1);
$clients->shouldReceive('findActive')->with(1)->andReturn(null);

$this->assertNull($guard->client($request));
}

public function test_clients_may_be_retrieved_from_cookies()
{
$resourceServer = Mockery::mock('League\OAuth2\Server\ResourceServer');
$userProvider = Mockery::mock('Illuminate\Contracts\Auth\UserProvider');
$tokens = Mockery::mock('Laravel\Passport\TokenRepository');
$clients = Mockery::mock('Laravel\Passport\ClientRepository');
$encrypter = new Illuminate\Encryption\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',
$encrypter->encrypt(JWT::encode([
'sub' => 1,
'aud' => 1,
'csrf' => 'token',
'expiry' => Carbon::now()->addMinutes(10)->getTimestamp(),
], str_repeat('a', 16)), false)
);

$clients->shouldReceive('findActive')->with(1)->andReturn($expectedClient = new TokenGuardTestClient);

$client = $guard->client($request);

$this->assertEquals($expectedClient, $client);
}
}

class TokenGuardTestUser
{
use Laravel\Passport\HasApiTokens;
}

class TokenGuardTestClient
{
}