Added field for specifying per app access token lifetime length.

This commit is contained in:
flash 2024-09-04 21:02:44 +00:00
parent 12d48cce2e
commit bf40942e72
4 changed files with 57 additions and 20 deletions

View file

@ -0,0 +1,12 @@
<?php
use Index\Data\IDbConnection;
use Index\Data\Migration\IDbMigration;
final class AccessLifetimeFieldForAppsTable_20240904_205008 implements IDbMigration {
public function migrate(IDbConnection $conn): void {
$conn->execute(<<<SQL
ALTER TABLE hau_apps
ADD COLUMN app_access_lifetime INT(10) UNSIGNED NULL DEFAULT NULL AFTER app_type;
SQL);
}
}

View file

@ -11,6 +11,7 @@ class AppInfo {
private string $website, private string $website,
private bool $trusted, private bool $trusted,
private string $type, private string $type,
private ?int $accessLifetime,
private ?int $refreshLifetime, private ?int $refreshLifetime,
private string $clientId, private string $clientId,
private string $clientSecret, private string $clientSecret,
@ -27,12 +28,13 @@ class AppInfo {
website: $result->getString(3), website: $result->getString(3),
trusted: $result->getBoolean(4), trusted: $result->getBoolean(4),
type: $result->getString(5), type: $result->getString(5),
refreshLifetime: $result->getIntegerOrNull(6), accessLifetime: $result->getIntegerOrNull(6),
clientId: $result->getString(7), refreshLifetime: $result->getIntegerOrNull(7),
clientSecret: $result->getString(8), clientId: $result->getString(8),
created: $result->getInteger(9), clientSecret: $result->getString(9),
updated: $result->getInteger(10), created: $result->getInteger(10),
deleted: $result->getIntegerOrNull(11), updated: $result->getInteger(11),
deleted: $result->getIntegerOrNull(12),
); );
} }
@ -73,6 +75,10 @@ class AppInfo {
return strcasecmp($this->type, 'confidential') === 0; return strcasecmp($this->type, 'confidential') === 0;
} }
public function getAccessTokenLifetime(): ?int {
return $this->accessLifetime;
}
public function getRefreshTokenLifetime(): ?int { public function getRefreshTokenLifetime(): ?int {
return $this->refreshLifetime; return $this->refreshLifetime;
} }

View file

@ -29,7 +29,7 @@ class AppsData {
throw new InvalidArgumentException('you must specify either $appId or $clientId'); throw new InvalidArgumentException('you must specify either $appId or $clientId');
$values = []; $values = [];
$query = 'SELECT app_id, app_name, app_summary, app_website, app_trusted, app_type, app_refresh_lifetime, app_client_id, app_client_secret, UNIX_TIMESTAMP(app_created), UNIX_TIMESTAMP(app_updated), UNIX_TIMESTAMP(app_deleted) FROM hau_apps'; $query = 'SELECT app_id, app_name, app_summary, app_website, app_trusted, app_type, app_access_lifetime, app_refresh_lifetime, app_client_id, app_client_secret, UNIX_TIMESTAMP(app_created), UNIX_TIMESTAMP(app_updated), UNIX_TIMESTAMP(app_deleted) FROM hau_apps';
$query .= sprintf(' WHERE %s = ?', $hasAppId ? 'app_id' : 'app_client_id'); $query .= sprintf(' WHERE %s = ?', $hasAppId ? 'app_id' : 'app_client_id');
if($hasDeleted) if($hasDeleted)
$query .= sprintf(' AND app_deleted %s NULL', $deleted ? 'IS NOT' : 'IS'); $query .= sprintf(' AND app_deleted %s NULL', $deleted ? 'IS NOT' : 'IS');
@ -60,6 +60,7 @@ class AppsData {
string $website = '', string $website = '',
?string $clientId = null, ?string $clientId = null,
#[\SensitiveParameter] ?string $clientSecret = null, #[\SensitiveParameter] ?string $clientSecret = null,
?int $accessLifetime = null,
?int $refreshLifetime = null ?int $refreshLifetime = null
): AppInfo { ): AppInfo {
if(trim($name) === '') if(trim($name) === '')
@ -72,6 +73,11 @@ class AppsData {
elseif(trim($clientId) === '') elseif(trim($clientId) === '')
throw new InvalidArgumentException('$clientId may not be empty'); throw new InvalidArgumentException('$clientId may not be empty');
if($accessLifetime !== null && $accessLifetime < 1)
throw new InvalidArgumentException('$accessLifetime must be null or greater than zero');
if($refreshLifetime !== null && $refreshLifetime < 0)
throw new InvalidArgumentException('$refreshLifetime must be null or a positive integer');
$summary = trim($summary); $summary = trim($summary);
$website = trim($website); $website = trim($website);
$clientSecret = $clientSecret === null ? '' : password_hash($clientSecret, self::CLIENT_SECRET_ALGO); $clientSecret = $clientSecret === null ? '' : password_hash($clientSecret, self::CLIENT_SECRET_ALGO);
@ -79,15 +85,16 @@ class AppsData {
if($type === self::TYPE_CONFIDENTIAL && $clientSecret === '') if($type === self::TYPE_CONFIDENTIAL && $clientSecret === '')
throw new InvalidArgumentException('$clientSecret must be specified for confidential clients'); throw new InvalidArgumentException('$clientSecret must be specified for confidential clients');
$stmt = $this->cache->get('INSERT INTO hau_apps (app_name, app_summary, app_website, app_trusted, app_type, app_refresh_lifetime, app_client_id, app_client_secret) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'); $stmt = $this->cache->get('INSERT INTO hau_apps (app_name, app_summary, app_website, app_trusted, app_type, app_access_lifetime, app_refresh_lifetime, app_client_id, app_client_secret) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
$stmt->addParameter(1, $name); $stmt->addParameter(1, $name);
$stmt->addParameter(2, $summary); $stmt->addParameter(2, $summary);
$stmt->addParameter(3, $website); $stmt->addParameter(3, $website);
$stmt->addParameter(4, $trusted ? 1 : 0); $stmt->addParameter(4, $trusted ? 1 : 0);
$stmt->addParameter(5, $type); $stmt->addParameter(5, $type);
$stmt->addParameter(6, $refreshLifetime); $stmt->addParameter(6, $accessLifetime);
$stmt->addParameter(7, $clientId); $stmt->addParameter(7, $refreshLifetime);
$stmt->addParameter(8, $clientSecret); $stmt->addParameter(8, $clientId);
$stmt->addParameter(9, $clientSecret);
$stmt->execute(); $stmt->execute();
return $this->getScopeInfo(appId: (string)$this->dbConn->getLastInsertId()); return $this->getScopeInfo(appId: (string)$this->dbConn->getLastInsertId());

View file

@ -61,12 +61,25 @@ class OAuth2Context {
$logAction(' Removed %d!', $pruned); $logAction(' Removed %d!', $pruned);
} }
public function createAccess(
AppInfo $appInfo,
string $scope = '',
?string $userInfo = null
): OAuth2AccessInfo {
return $this->tokens->createAccess(
$appInfo,
userId: $userInfo,
scope: $scope,
lifetime: $appInfo->getAccessTokenLifetime()
);
}
public function createRefresh( public function createRefresh(
AppInfo $appInfo, AppInfo $appInfo,
OAuth2AccessInfo $accessInfo OAuth2AccessInfo $accessInfo
): OAuth2RefreshInfo { ): OAuth2RefreshInfo {
return $this->tokens->createRefresh( return $this->tokens->createRefresh(
$accessInfo->getAppId(), $appInfo,
$accessInfo, $accessInfo,
userId: $accessInfo->getUserId(), userId: $accessInfo->getUserId(),
scope: $accessInfo->getScope(), scope: $accessInfo->getScope(),
@ -172,11 +185,10 @@ class OAuth2Context {
$scope = $this->checkAndBuildScopeString($appInfo, $authsInfo->getScope(), true); $scope = $this->checkAndBuildScopeString($appInfo, $authsInfo->getScope(), true);
$tokensData = $this->getTokensData(); $accessInfo = $this->createAccess(
$accessInfo = $tokensData->createAccess(
$appInfo, $appInfo,
$scope,
$authsInfo->getUserId(), $authsInfo->getUserId(),
scope: $scope,
); );
if($authsInfo->getScope() === $scope) if($authsInfo->getScope() === $scope)
@ -226,10 +238,10 @@ class OAuth2Context {
$scope = $this->checkAndBuildScopeString($appInfo, $scope, true); $scope = $this->checkAndBuildScopeString($appInfo, $scope, true);
$accessInfo = $tokensData->createAccess( $accessInfo = $this->createAccess(
$appInfo, $appInfo,
$scope,
$refreshInfo->getUserId(), $refreshInfo->getUserId(),
scope: $scope,
); );
if($refreshInfo->getScope() === $scope) if($refreshInfo->getScope() === $scope)
@ -256,7 +268,7 @@ class OAuth2Context {
$requestedScope = $scope; $requestedScope = $scope;
$scope = $this->checkAndBuildScopeString($appInfo, $requestedScope, true); $scope = $this->checkAndBuildScopeString($appInfo, $requestedScope, true);
$accessInfo = $this->getTokensData()->createAccess($appInfo, scope: $scope); $accessInfo = $this->createAccess($appInfo, $scope);
if($requestedScope !== null && $scope === '') if($requestedScope !== null && $scope === '')
return [ return [
@ -323,10 +335,10 @@ class OAuth2Context {
$scope = $this->checkAndBuildScopeString($appInfo, $deviceInfo->getScope(), true); $scope = $this->checkAndBuildScopeString($appInfo, $deviceInfo->getScope(), true);
$tokensData = $this->getTokensData(); $tokensData = $this->getTokensData();
$accessInfo = $tokensData->createAccess( $accessInfo = $this->createAccess(
$appInfo, $appInfo,
$scope,
$deviceInfo->getUserId(), $deviceInfo->getUserId(),
scope: $scope,
); );
// 'scope' only has to be in the response if it differs from what was requested // 'scope' only has to be in the response if it differs from what was requested