Implemented desktop application type

This commit is contained in:
ErickSkrauch
2025-01-15 14:13:08 +01:00
parent 3bba99a757
commit 1c2969a4be
22 changed files with 183 additions and 214 deletions

View File

@@ -31,7 +31,6 @@ final class AuthorizationServerFactory {
);
/** @noinspection PhpUnhandledExceptionInspection */
$authCodeGrant = new Grants\AuthCodeGrant($authCodesRepo, $refreshTokensRepo, new DateInterval('PT10M'));
$authCodeGrant->disableRequireCodeChallengeForPublicClients();
$authServer->enableGrantType($authCodeGrant, $accessTokenTTL);
$authCodeGrant->setScopeRepository($publicScopesRepo); // Change repository after enabling

View File

@@ -20,7 +20,8 @@ final class ClientEntity implements ClientEntityInterface {
string $id,
string $name,
string|array $redirectUri,
private readonly bool $isTrusted,
private readonly bool $isTrusted = false,
public readonly ?OauthClient $model = null,
) {
$this->identifier = $id;
$this->name = $name;
@@ -29,15 +30,20 @@ final class ClientEntity implements ClientEntityInterface {
public static function fromModel(OauthClient $model): self {
return new self(
$model->id, // @phpstan-ignore argument.type
$model->name,
$model->redirect_uri ?: '',
(bool)$model->is_trusted,
id: $model->id, // @phpstan-ignore argument.type
name: $model->name,
redirectUri: $model->redirect_uri ?: '',
isTrusted: (bool)$model->is_trusted,
model: $model,
);
}
public function isConfidential(): bool {
return true;
return match ($this->model->type) {
OauthClient::TYPE_WEB_APPLICATION => true,
OauthClient::TYPE_DESKTOP_APPLICATION => false,
OauthClient::TYPE_MINECRAFT_SERVER => true,
};
}
public function isTrusted(): bool {

View File

@@ -3,6 +3,8 @@ declare(strict_types=1);
namespace common\components\OAuth2\Grants;
use common\components\OAuth2\Entities\ClientEntity;
use common\models\OauthClient;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\EventEmitting\EventEmitter;
use League\OAuth2\Server\Exception\OAuthServerException;
@@ -14,15 +16,35 @@ trait ValidateRedirectUriTrait {
abstract public function getEmitter(): EventEmitter;
/**
* Override the original method since we need a custom validation logic based on the client type.
* @inheritDoc
*/
protected function validateRedirectUri(
string $redirectUri,
ClientEntityInterface $client,
ServerRequestInterface $request,
): void {
$allowedRedirectUris = (array)$client->getRedirectUri();
foreach ($allowedRedirectUris as $allowedRedirectUri) {
if (StringHelper::startsWith($redirectUri, $allowedRedirectUri)) {
return;
if ($client instanceof ClientEntity && $client->model?->type === OauthClient::TYPE_DESKTOP_APPLICATION) {
$uri = parse_url($redirectUri);
if ($uri) {
// Allow any custom scheme, that is not http
if ($uri['scheme'] !== 'http' && $uri['scheme'] !== 'https') {
return;
}
// If it's a http, than should allow only redirection to the local machine
if (in_array($uri['host'], ['localhost', '127.0.0.1', '[::1]'])) {
return;
}
}
} else {
// The original implementation checks url too strictly (port and path must exactly match).
// It's nice to have, but we made it this way earlier and so we must keep the same behavior as long as possible
foreach ((array)$client->getRedirectUri() as $allowedRedirectUri) {
if (StringHelper::startsWith($redirectUri, $allowedRedirectUri)) {
return;
}
}
}

View File

@@ -25,11 +25,11 @@ final class ClientRepository implements ClientRepositoryInterface {
return false;
}
if ($client->type !== OauthClient::TYPE_APPLICATION) {
if (!in_array($client->type, [OauthClient::TYPE_WEB_APPLICATION, OauthClient::TYPE_DESKTOP_APPLICATION], true)) {
return false;
}
if (!empty($clientSecret) && $clientSecret !== $client->secret) {
if ($client->type === OauthClient::TYPE_WEB_APPLICATION && !empty($clientSecret) && $clientSecret !== $client->secret) {
return false;
}
@@ -37,12 +37,7 @@ final class ClientRepository implements ClientRepositoryInterface {
}
private function findModel(string $id): ?OauthClient {
$client = OauthClient::findOne(['id' => $id]);
if ($client === null || $client->type !== OauthClient::TYPE_APPLICATION) {
return null;
}
return $client;
return OauthClient::findOne(['id' => $id]);
}
}

View File

@@ -12,7 +12,7 @@ use yii\db\ActiveRecord;
* Fields:
* @property string $id
* @property string $secret
* @property string $type
* @property self::TYPE_* $type
* @property string $name
* @property string $description
* @property string|null $redirect_uri
@@ -29,14 +29,14 @@ use yii\db\ActiveRecord;
*/
class OauthClient extends ActiveRecord {
public const TYPE_APPLICATION = 'application';
public const TYPE_MINECRAFT_SERVER = 'minecraft-server';
public const TYPE_MINECRAFT_GAME_LAUNCHER = 'minecraft-game-launcher';
public const string TYPE_WEB_APPLICATION = 'application';
public const string TYPE_DESKTOP_APPLICATION = 'desktop-application';
public const string TYPE_MINECRAFT_SERVER = 'minecraft-server';
/**
* Abstract oauth_client, used to
*/
public const UNAUTHORIZED_MINECRAFT_GAME_LAUNCHER = 'unauthorized_minecraft_game_launcher';
public const string UNAUTHORIZED_MINECRAFT_GAME_LAUNCHER = 'unauthorized_minecraft_game_launcher';
public static function tableName(): string {
return 'oauth_clients';
@@ -55,10 +55,13 @@ class OauthClient extends ActiveRecord {
$this->secret = Yii::$app->security->generateRandomString(64);
}
public function getAccount(): ActiveQuery {
public function getAccount(): AccountQuery {
return $this->hasOne(Account::class, ['id' => 'account_id']);
}
/**
* @return \yii\db\ActiveQuery<\common\models\OauthSession>
*/
public function getSessions(): ActiveQuery {
return $this->hasMany(OauthSession::class, ['client_id' => 'id']);
}