Skip to content

Commit

Permalink
[13.x] Configure the user provider for PAT (#1768)
Browse files Browse the repository at this point in the history
* add provider support for pat

* fix tests

* formatting

* additional check
  • Loading branch information
hafezdivandari authored Jul 5, 2024
1 parent 0ee1d8b commit ef640ef
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 22 deletions.
4 changes: 4 additions & 0 deletions src/Bridge/PersonalAccessGrant.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ public function respondToAccessTokenRequest(
$scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope));
$userIdentifier = $this->getRequestParameter('user_id', $request);

if (! $userIdentifier) {
throw OAuthServerException::invalidRequest('user_id');
}

// Finalize the requested scopes
$scopes = $this->scopeRepository->finalizeScopes(
$scopes,
Expand Down
22 changes: 21 additions & 1 deletion src/HasApiTokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,30 @@ public function tokenCan($scope)
public function createToken($name, array $scopes = [])
{
return Container::getInstance()->make(PersonalAccessTokenFactory::class)->make(
$this->getAuthIdentifier(), $name, $scopes
$this->getAuthIdentifier(), $name, $scopes, $this->getProvider()
);
}

/**
* Get the user provider name.
*
* @return string|null
*/
public function getProvider(): ?string
{
$providers = collect(config('auth.guards'))->where('driver', 'passport')->pluck('provider')->all();

foreach (config('auth.providers') as $provider => $config) {
if (in_array($provider, $providers)
&& (($config['driver'] === 'eloquent' && is_a($this, $config['model']))
|| ($config['driver'] === 'database' && $config['table'] === $this->getTable()))) {
return $provider;
}
}

return null;
}

/**
* Set the current access token for the user.
*
Expand Down
30 changes: 24 additions & 6 deletions src/PersonalAccessTokenFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ class PersonalAccessTokenFactory
*/
protected $server;

/**
* The client repository instance.
*
* @var \Laravel\Passport\ClientRepository
*/
protected $clients;

/**
* The token repository instance.
*
Expand All @@ -36,15 +43,20 @@ class PersonalAccessTokenFactory
* Create a new personal access token factory instance.
*
* @param \League\OAuth2\Server\AuthorizationServer $server
* @param \Laravel\Passport\ClientRepository $clients
* @param \Laravel\Passport\TokenRepository $tokens
* @param \Lcobucci\JWT\Parser $jwt
* @return void
*/
public function __construct(AuthorizationServer $server, TokenRepository $tokens, JwtParser $jwt)
public function __construct(AuthorizationServer $server,
ClientRepository $clients,
TokenRepository $tokens,
JwtParser $jwt)
{
$this->jwt = $jwt;
$this->tokens = $tokens;
$this->server = $server;
$this->clients = $clients;
}

/**
Expand All @@ -53,12 +65,13 @@ public function __construct(AuthorizationServer $server, TokenRepository $tokens
* @param mixed $userId
* @param string $name
* @param string[] $scopes
* @param string|null $provider
* @return \Laravel\Passport\PersonalAccessTokenResult
*/
public function make($userId, string $name, array $scopes = [])
public function make($userId, string $name, array $scopes = [], ?string $provider = null)
{
$response = $this->dispatchRequestToAuthorizationServer(
$this->createRequest($userId, $scopes)
$this->createRequest($userId, $scopes, $provider)
);

$token = tap($this->findAccessToken($response), function ($token) use ($userId, $name) {
Expand All @@ -78,13 +91,18 @@ public function make($userId, string $name, array $scopes = [])
*
* @param mixed $userId
* @param string[] $scopes
* @param string|null $provider
* @return \Psr\Http\Message\ServerRequestInterface
*/
protected function createRequest($userId, array $scopes)
protected function createRequest($userId, array $scopes, ?string $provider)
{
$config = config('passport.personal_access_client');
$config = $provider
? config("passport.personal_access_client.$provider", config('passport.personal_access_client'))
: config('passport.personal_access_client');

$client = isset($config['id']) ? $this->clients->findActive($config['id']) : null;

if (! $config) {
if (! $client || ($client->provider && $client->provider !== $provider)) {
throw new RuntimeException(
'Personal access client not found. Please create one and set the `PASSPORT_PERSONAL_ACCESS_CLIENT_ID` and `PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET` environment variables.'
);
Expand Down
37 changes: 37 additions & 0 deletions tests/Feature/HasApiTokensTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Laravel\Passport\Tests\Feature;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;
use Orchestra\Testbench\Concerns\WithLaravelMigrations;
use Workbench\Database\Factories\UserFactory;

class HasApiTokensTest extends PassportTestCase
{
use WithLaravelMigrations;

public function testGetProvider()
{
config([
'auth.providers.admins' => ['driver' => 'eloquent', 'model' => AdminHasApiTokensStub::class],
'auth.guards.api-admins' => ['driver' => 'passport', 'provider' => 'admins'],
'auth.providers.customers' => ['driver' => 'database', 'table' => 'customer_has_api_tokens_stubs'],
'auth.guards.api-customers' => ['driver' => 'passport', 'provider' => 'customers'],
]);

$this->assertSame('users', UserFactory::new()->create()->getProvider());
$this->assertSame('admins', (new AdminHasApiTokensStub)->getProvider());
$this->assertSame('customers', (new CustomerHasApiTokensStub)->getProvider());
}
}

class AdminHasApiTokensStub extends Authenticatable
{
use HasApiTokens;
}

class CustomerHasApiTokensStub extends Authenticatable
{
use HasApiTokens;
}
60 changes: 60 additions & 0 deletions tests/Feature/PersonalAccessTokenFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
namespace Laravel\Passport\Tests\Feature;

use Illuminate\Contracts\Hashing\Hasher;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\Client;
use Laravel\Passport\Database\Factories\ClientFactory;
use Laravel\Passport\HasApiTokens;
use Laravel\Passport\Passport;
use Laravel\Passport\PersonalAccessTokenResult;
use Orchestra\Testbench\Concerns\WithLaravelMigrations;
Expand Down Expand Up @@ -41,4 +43,62 @@ public function testIssueToken()
$this->assertSame($user->getAuthIdentifier(), $result->token->user_id);
$this->assertSame(['bar'], $result->token->scopes);
}

public function testIssueTokenWithDifferentProviders()
{
$client = ClientFactory::new()->asPersonalAccessTokenClient()->create();
$adminClient = ClientFactory::new()->asPersonalAccessTokenClient()->create(['provider' => 'admins']);
$customerClient = ClientFactory::new()->asPersonalAccessTokenClient()->create(['provider' => 'customers']);

config([
'auth.providers.admins' => ['driver' => 'eloquent', 'model' => AdminProviderStub::class],
'auth.guards.api-admins' => ['driver' => 'passport', 'provider' => 'admins'],
'auth.providers.customers' => ['driver' => 'database', 'table' => 'customer_provider_stubs'],
'auth.guards.api-customers' => ['driver' => 'passport', 'provider' => 'customers'],
'passport.personal_access_client' => ['id' => $client->getKey(), 'secret' => $client->plainSecret],
'passport.personal_access_client.admins' => ['id' => $adminClient->getKey(), 'secret' => $adminClient->plainSecret],
'passport.personal_access_client.customers' => ['id' => $customerClient->getKey(), 'secret' => $customerClient->plainSecret],
]);

$user = UserFactory::new()->create();
$userToken = $user->createToken('test user');

$admin = new AdminProviderStub;
$adminToken = $admin->createToken('test admin');

$customer = new CustomerProviderStub;
$customerToken = $customer->createToken('test customer');

$this->assertInstanceOf(PersonalAccessTokenResult::class, $userToken);
$this->assertSame($client->getKey(), $userToken->token->client_id);
$this->assertSame($user->getAuthIdentifier(), $userToken->token->user_id);

$this->assertInstanceOf(PersonalAccessTokenResult::class, $adminToken);
$this->assertSame($adminClient->getKey(), $adminToken->token->client_id);
$this->assertSame($admin->getAuthIdentifier(), $adminToken->token->user_id);

$this->assertInstanceOf(PersonalAccessTokenResult::class, $customerToken);
$this->assertSame($customerClient->getKey(), $customerToken->token->client_id);
$this->assertSame($customer->getAuthIdentifier(), $customerToken->token->user_id);
}
}

class AdminProviderStub extends Authenticatable
{
use HasApiTokens;

public function getAuthIdentifier()
{
return 'foo';
}
}

class CustomerProviderStub extends Authenticatable
{
use HasApiTokens;

public function getAuthIdentifier()
{
return 3;
}
}
17 changes: 2 additions & 15 deletions tests/Unit/HasApiTokensTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
namespace Laravel\Passport\Tests\Unit;

use Illuminate\Container\Container;
use Laravel\Passport\AccessToken;
use Laravel\Passport\HasApiTokens;
use Laravel\Passport\PersonalAccessTokenFactory;
use Mockery as m;
use PHPUnit\Framework\TestCase;

Expand All @@ -19,26 +19,13 @@ protected function tearDown(): void
public function test_token_can_indicates_if_token_has_given_scope()
{
$user = new HasApiTokensTestStub;
$token = m::mock();
$token = m::mock(AccessToken::class);
$token->shouldReceive('can')->with('scope')->andReturn(true);
$token->shouldReceive('can')->with('another-scope')->andReturn(false);

$this->assertTrue($user->withAccessToken($token)->tokenCan('scope'));
$this->assertFalse($user->withAccessToken($token)->tokenCan('another-scope'));
}

public function test_token_can_be_created()
{
$this->expectNotToPerformAssertions();

$container = new Container;
Container::setInstance($container);
$container->instance(PersonalAccessTokenFactory::class, $factory = m::mock());
$factory->shouldReceive('make')->once()->with(1, 'name', ['scopes']);
$user = new HasApiTokensTestStub;

$user->createToken('name', ['scopes']);
}
}

class HasApiTokensTestStub
Expand Down

0 comments on commit ef640ef

Please sign in to comment.