Skip to content

[13.x] Configure the user provider for PAT #1768

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

Merged
merged 4 commits into from
Jul 5, 2024
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
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)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be the same as the check on TokenGuard:

if (! $client ||
($client->provider &&
$client->provider !== $this->provider->getProviderName())) {

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