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

[13.x] Make client RFC compatible #1744

Merged
merged 31 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3563bc2
make client RFC compatible
hafezdivandari May 18, 2024
a521a78
formatting
hafezdivandari May 18, 2024
e906821
drop support for Laravel 9
hafezdivandari May 18, 2024
7b33d5d
update upgrade guide
hafezdivandari May 19, 2024
45c5143
Merge branch '13.x' into 13.x-refactor-client
hafezdivandari May 30, 2024
5bca853
wip
hafezdivandari May 30, 2024
7d7addc
Merge branch '13.x' into 13.x-refactor-client
hafezdivandari Jun 4, 2024
38a36be
Merge branch '13.x' into 13.x-refactor-client
hafezdivandari Jun 27, 2024
aae97b1
Merge branch '13.x' into 13.x-refactor-client
hafezdivandari Jun 27, 2024
da3edf0
revert some changes
hafezdivandari Jun 27, 2024
676b43c
Merge branch '13.x' into 13.x-refactor-client
hafezdivandari Jul 29, 2024
d8e89ca
fix tests
hafezdivandari Jul 29, 2024
3c66423
wip
hafezdivandari Jul 29, 2024
e4931d5
fix tests
hafezdivandari Jul 29, 2024
2784abf
wip
hafezdivandari Jul 29, 2024
7edb0ef
wip
hafezdivandari Jul 29, 2024
14df618
wip
hafezdivandari Jul 29, 2024
82bc8da
wip
hafezdivandari Jul 29, 2024
baa6395
wip
hafezdivandari Jul 29, 2024
a579f4f
wip
hafezdivandari Jul 30, 2024
5c7944a
wip
hafezdivandari Jul 30, 2024
1149e9c
wip
hafezdivandari Jul 30, 2024
405d559
formatting
hafezdivandari Jul 30, 2024
1fc5d18
formatting
hafezdivandari Jul 30, 2024
407082f
formatting
hafezdivandari Jul 30, 2024
dfdec66
wip
hafezdivandari Aug 2, 2024
7235890
revert unrelated changes
hafezdivandari Aug 4, 2024
d3136c4
fix backward compatibility
hafezdivandari Aug 4, 2024
6aeb388
formatting
hafezdivandari Aug 4, 2024
86694ee
fix bc on update
hafezdivandari Aug 4, 2024
bf7ce0d
add tests for clients without grant_types
hafezdivandari Aug 4, 2024
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
14 changes: 5 additions & 9 deletions database/factories/ClientFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ public function definition()
'user_id' => null,
'name' => $this->faker->company(),
'secret' => Str::random(40),
'redirect' => $this->faker->url(),
'personal_access_client' => false,
'password_client' => false,
'redirect_uris' => [$this->faker->url()],
'grant_types' => ['authorization_code', 'refresh_token'],
'revoked' => false,
];
}
Expand All @@ -47,8 +46,7 @@ public function definition()
public function asPasswordClient()
{
return $this->state([
'personal_access_client' => false,
'password_client' => true,
'grant_types' => ['password', 'refresh_token'],
]);
}

Expand All @@ -60,8 +58,7 @@ public function asPasswordClient()
public function asPersonalAccessTokenClient()
{
return $this->state([
'personal_access_client' => true,
'password_client' => false,
'grant_types' => ['personal_access'],
]);
}

Expand All @@ -73,8 +70,7 @@ public function asPersonalAccessTokenClient()
public function asClientCredentials()
{
return $this->state([
'personal_access_client' => false,
'password_client' => false,
'grant_types' => ['client_credentials'],
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ public function up(): void
$table->string('name');
$table->string('secret')->nullable();
$table->string('provider')->nullable();
$table->text('redirect');
$table->boolean('personal_access_client');
$table->boolean('password_client');
$table->text('redirect_uris');
$table->text('grant_types');
$table->boolean('revoked');
$table->timestamps();
});
Expand Down
4 changes: 2 additions & 2 deletions src/Bridge/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ class Client implements ClientEntityInterface
public function __construct(
string $identifier,
string $name,
string $redirectUri,
array $redirectUri,
bool $isConfidential = false,
?string $provider = null
) {
$this->setIdentifier($identifier);

$this->name = $name;
$this->isConfidential = $isConfidential;
$this->redirectUri = explode(',', $redirectUri);
$this->redirectUri = $redirectUri;
$this->provider = $provider;
}
}
14 changes: 2 additions & 12 deletions src/Bridge/ClientRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public function getClientEntity(string $clientIdentifier): ?ClientEntityInterfac
return new Client(
$clientIdentifier,
$record->name,
$record->redirect,
$record->redirect_uris,
$record->confidential(),
$record->provider
);
Expand Down Expand Up @@ -71,17 +71,7 @@ public function validateClient(string $clientIdentifier, ?string $clientSecret,
*/
protected function handlesGrant(ClientModel $record, string $grantType): bool
{
if (! $record->hasGrantType($grantType)) {
return false;
}

return match ($grantType) {
'authorization_code' => ! $record->firstParty(),
'personal_access' => $record->personal_access_client && $record->confidential(),
'password' => $record->password_client,
'client_credentials' => $record->confidential(),
default => true,
};
return $record->hasGrantType($grantType);
}

/**
Expand Down
32 changes: 28 additions & 4 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Laravel\Passport;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Hash;
Expand Down Expand Up @@ -44,6 +45,7 @@ class Client extends Model
protected $casts = [
'grant_types' => 'array',
'scopes' => 'array',
'redirect_uris' => 'array',
'personal_access_client' => 'bool',
'password_client' => 'bool',
'revoked' => 'bool',
Expand Down Expand Up @@ -132,14 +134,30 @@ public function setSecretAttribute($value)
$this->attributes['secret'] = is_null($value) ? $value : Hash::make($value);
}

/**
* Get the client's redirect URIs.
*/
protected function redirectUris(): Attribute
{
return Attribute::make(
get: function (?string $value, array $attributes) {
if (isset($value)) {
return $this->fromJson($value);
}

return empty($attributes['redirect']) ? [] : explode(',', $attributes['redirect']);
},
);
}

/**
* Determine if the client is a "first party" client.
*
* @return bool
*/
public function firstParty()
{
return $this->personal_access_client || $this->password_client;
return $this->hasGrantType('personal_access') || $this->hasGrantType('password');
}

/**
Expand All @@ -160,11 +178,17 @@ public function skipsAuthorization()
*/
public function hasGrantType($grantType)
{
if (! isset($this->attributes['grant_types']) || ! is_array($this->grant_types)) {
return true;
if (isset($this->attributes['grant_types']) && is_array($this->grant_types)) {
return in_array($grantType, $this->grant_types);
}

return in_array($grantType, $this->grant_types);
return match ($grantType) {
'authorization_code' => ! $this->personal_access_client && ! $this->password_client,
'personal_access' => $this->personal_access_client && $this->confidential(),
'password' => $this->password_client,
'client_credentials' => $this->confidential(),
default => true,
};
}

/**
Expand Down
124 changes: 80 additions & 44 deletions src/ClientRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Laravel\Passport;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Str;

class ClientRepository
Expand Down Expand Up @@ -78,75 +79,110 @@ public function activeForUser($userId)
/**
* Store a new client.
*
* @param int|null $userId
* @param string $name
* @param string $redirect
* @param string|null $provider
* @param bool $personalAccess
* @param bool $password
* @param bool $confidential
* @return \Laravel\Passport\Client
* @param string[] $redirectUris
* @param string[] $grantTypes
*/
public function create($userId, $name, $redirect, $provider = null, $personalAccess = false, $password = false, $confidential = true)
{
$client = Passport::client()->forceFill([
'user_id' => $userId,
protected function create(
string $name,
array $grantTypes,
array $redirectUris = [],
?string $provider = null,
bool $confidential = true,
?Authenticatable $user = null
): Client {
$client = Passport::client();
$columns = $client->getConnection()->getSchemaBuilder()->getColumnListing($client->getTable());
Copy link
Contributor

Choose a reason for hiding this comment

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

How about using a config option, instead of querying the column details?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also thought about a new config option, but it seems to be redundant when we can easily check for existing columns?

Copy link
Contributor

Choose a reason for hiding this comment

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

It would save one query. But I will leave that decision to you / the Laravel Team.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah but client creation / updating doesn't happen frequently. Lets see what maintainers decide.


$attributes = [
'name' => $name,
'secret' => ($confidential || $personalAccess) ? Str::random(40) : null,
'secret' => $confidential ? Str::random(40) : null,
'provider' => $provider,
'redirect' => $redirect,
'personal_access_client' => $personalAccess,
'password_client' => $password,
'revoked' => false,
]);

$client->save();

return $client;
...(in_array('redirect_uris', $columns) ? [
'redirect_uris' => $redirectUris,
] : [
'redirect' => implode(',', $redirectUris),
]),
...(in_array('grant_types', $columns) ? [
'grant_types' => $grantTypes,
] : [
'personal_access_client' => in_array('personal_access', $grantTypes),
'password_client' => in_array('password', $grantTypes),
]),
];

return $user
? $user->clients()->forceCreate($attributes)
: $client->forceCreate($attributes);
}

/**
* Store a new personal access token client.
*
* @param int|null $userId
* @param string $name
* @param string $redirect
* @return \Laravel\Passport\Client
*/
public function createPersonalAccessClient($userId, $name, $redirect)
public function createPersonalAccessGrantClient(string $name, ?string $provider = null): Client
{
return $this->create($userId, $name, $redirect, null, true);
return $this->create($name, ['personal_access'], [], $provider);
}

/**
* Store a new password grant client.
*/
public function createPasswordGrantClient(string $name, ?string $provider = null): Client
{
return $this->create($name, ['password', 'refresh_token'], [], $provider);
}

/**
* Store a new client credentials grant client.
*/
public function createClientCredentialsGrantClient(string $name): Client
{
return $this->create($name, ['client_credentials']);
}

/**
* Store a new implicit grant client.
*
* @param int|null $userId
* @param string $name
* @param string $redirect
* @param string|null $provider
* @return \Laravel\Passport\Client
* @param string[] $redirectUris
*/
public function createPasswordGrantClient($userId, $name, $redirect, $provider = null)
public function createImplicitGrantClient(string $name, array $redirectUris): Client
{
return $this->create($userId, $name, $redirect, $provider, false, true);
return $this->create($name, ['implicit'], $redirectUris);
}

/**
* Store a new authorization code grant client.
*
* @param string[] $redirectUris
*/
public function createAuthorizationCodeGrantClient(
string $name,
array $redirectUris,
bool $confidential = true,
?Authenticatable $user = null
): Client {
return $this->create(
$name, ['authorization_code', 'refresh_token'], $redirectUris, null, $confidential, $user
);
}

/**
* Update the given client.
*
* @param \Laravel\Passport\Client $client
* @param string $name
* @param string $redirect
* @return \Laravel\Passport\Client
* @param string[] $redirectUris
*/
public function update(Client $client, $name, $redirect)
public function update(Client $client, string $name, array $redirectUris): bool
{
$client->forceFill([
'name' => $name, 'redirect' => $redirect,
])->save();
$columns = $client->getConnection()->getSchemaBuilder()->getColumnListing($client->getTable());

return $client;
return $client->forceFill([
'name' => $name,
...(in_array('redirect_uris', $columns) ? [
'redirect_uris' => $redirectUris,
] : [
'redirect' => implode(',', $redirectUris),
]),
])->save();
}

/**
Expand Down
Loading