From bf40942e72f90c54cda4c1f3fdd96e562f95ea43 Mon Sep 17 00:00:00 2001 From: flashwave Date: Wed, 4 Sep 2024 21:02:44 +0000 Subject: [PATCH] Added field for specifying per app access token lifetime length. --- ...8_access_lifetime_field_for_apps_table.php | 12 ++++++++ src/Apps/AppInfo.php | 18 +++++++---- src/Apps/AppsData.php | 17 +++++++---- src/OAuth2/OAuth2Context.php | 30 +++++++++++++------ 4 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 database/2024_09_04_205008_access_lifetime_field_for_apps_table.php diff --git a/database/2024_09_04_205008_access_lifetime_field_for_apps_table.php b/database/2024_09_04_205008_access_lifetime_field_for_apps_table.php new file mode 100644 index 0000000..54e6556 --- /dev/null +++ b/database/2024_09_04_205008_access_lifetime_field_for_apps_table.php @@ -0,0 +1,12 @@ +execute(<<getString(3), trusted: $result->getBoolean(4), type: $result->getString(5), - refreshLifetime: $result->getIntegerOrNull(6), - clientId: $result->getString(7), - clientSecret: $result->getString(8), - created: $result->getInteger(9), - updated: $result->getInteger(10), - deleted: $result->getIntegerOrNull(11), + accessLifetime: $result->getIntegerOrNull(6), + refreshLifetime: $result->getIntegerOrNull(7), + clientId: $result->getString(8), + clientSecret: $result->getString(9), + created: $result->getInteger(10), + updated: $result->getInteger(11), + deleted: $result->getIntegerOrNull(12), ); } @@ -73,6 +75,10 @@ class AppInfo { return strcasecmp($this->type, 'confidential') === 0; } + public function getAccessTokenLifetime(): ?int { + return $this->accessLifetime; + } + public function getRefreshTokenLifetime(): ?int { return $this->refreshLifetime; } diff --git a/src/Apps/AppsData.php b/src/Apps/AppsData.php index c9dca98..69b6392 100644 --- a/src/Apps/AppsData.php +++ b/src/Apps/AppsData.php @@ -29,7 +29,7 @@ class AppsData { throw new InvalidArgumentException('you must specify either $appId or $clientId'); $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'); if($hasDeleted) $query .= sprintf(' AND app_deleted %s NULL', $deleted ? 'IS NOT' : 'IS'); @@ -60,6 +60,7 @@ class AppsData { string $website = '', ?string $clientId = null, #[\SensitiveParameter] ?string $clientSecret = null, + ?int $accessLifetime = null, ?int $refreshLifetime = null ): AppInfo { if(trim($name) === '') @@ -72,6 +73,11 @@ class AppsData { elseif(trim($clientId) === '') 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); $website = trim($website); $clientSecret = $clientSecret === null ? '' : password_hash($clientSecret, self::CLIENT_SECRET_ALGO); @@ -79,15 +85,16 @@ class AppsData { if($type === self::TYPE_CONFIDENTIAL && $clientSecret === '') 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(2, $summary); $stmt->addParameter(3, $website); $stmt->addParameter(4, $trusted ? 1 : 0); $stmt->addParameter(5, $type); - $stmt->addParameter(6, $refreshLifetime); - $stmt->addParameter(7, $clientId); - $stmt->addParameter(8, $clientSecret); + $stmt->addParameter(6, $accessLifetime); + $stmt->addParameter(7, $refreshLifetime); + $stmt->addParameter(8, $clientId); + $stmt->addParameter(9, $clientSecret); $stmt->execute(); return $this->getScopeInfo(appId: (string)$this->dbConn->getLastInsertId()); diff --git a/src/OAuth2/OAuth2Context.php b/src/OAuth2/OAuth2Context.php index 908b6e1..bddef7d 100644 --- a/src/OAuth2/OAuth2Context.php +++ b/src/OAuth2/OAuth2Context.php @@ -61,12 +61,25 @@ class OAuth2Context { $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( AppInfo $appInfo, OAuth2AccessInfo $accessInfo ): OAuth2RefreshInfo { return $this->tokens->createRefresh( - $accessInfo->getAppId(), + $appInfo, $accessInfo, userId: $accessInfo->getUserId(), scope: $accessInfo->getScope(), @@ -172,11 +185,10 @@ class OAuth2Context { $scope = $this->checkAndBuildScopeString($appInfo, $authsInfo->getScope(), true); - $tokensData = $this->getTokensData(); - $accessInfo = $tokensData->createAccess( + $accessInfo = $this->createAccess( $appInfo, + $scope, $authsInfo->getUserId(), - scope: $scope, ); if($authsInfo->getScope() === $scope) @@ -226,10 +238,10 @@ class OAuth2Context { $scope = $this->checkAndBuildScopeString($appInfo, $scope, true); - $accessInfo = $tokensData->createAccess( + $accessInfo = $this->createAccess( $appInfo, + $scope, $refreshInfo->getUserId(), - scope: $scope, ); if($refreshInfo->getScope() === $scope) @@ -256,7 +268,7 @@ class OAuth2Context { $requestedScope = $scope; $scope = $this->checkAndBuildScopeString($appInfo, $requestedScope, true); - $accessInfo = $this->getTokensData()->createAccess($appInfo, scope: $scope); + $accessInfo = $this->createAccess($appInfo, $scope); if($requestedScope !== null && $scope === '') return [ @@ -323,10 +335,10 @@ class OAuth2Context { $scope = $this->checkAndBuildScopeString($appInfo, $deviceInfo->getScope(), true); $tokensData = $this->getTokensData(); - $accessInfo = $tokensData->createAccess( + $accessInfo = $this->createAccess( $appInfo, + $scope, $deviceInfo->getUserId(), - scope: $scope, ); // 'scope' only has to be in the response if it differs from what was requested