diff --git a/src/DefaultInjectorBindings.php b/src/DefaultInjectorBindings.php index 5f9da666..e87385f0 100644 --- a/src/DefaultInjectorBindings.php +++ b/src/DefaultInjectorBindings.php @@ -78,6 +78,10 @@ use Horde\Core\Factory\OAuthFlowStoreFactory; use Horde\Core\Factory\PermissionServiceFactory; use Horde\Core\Factory\PrefsServiceFactory; +use Horde\Core\Service\Factory\CredentialStoreFactory; +use Horde\Core\Service\Factory\CredentialProvisioningStrategyFactory; +use Horde\Core\Service\Factory\PasswordServiceAuthorizationServiceFactory; +use Horde\Core\Service\Factory\ServiceCredentialManagerFactory; use Horde\Core\Factory\RegistryConfigLoaderFactory; use Horde\Core\Factory\RouteUrlWriterFactory; use Horde\Core\Factory\RuntimeRoutesProviderFactory; @@ -94,6 +98,8 @@ use Horde\Core\Middleware\ErrorFilter; use Horde\Core\Middleware\OAuthConsentMiddleware; use Horde\Core\Service\ApplicationService; +use Horde\Core\Service\CredentialStore; +use Horde\Core\Service\CredentialProvisioningStrategy; use Horde\Core\Service\GroupService; use Horde\Core\Service\HordeDbService; use Horde\Core\Service\HordeLdapService; @@ -101,8 +107,10 @@ use Horde\Core\Service\OAuthHttpClientService; use Horde\Core\Service\OAuthProviderConfigRepository; use Horde\Core\Service\OAuthTokenService; +use Horde\Core\Service\PasswordServiceAuthorizationService; use Horde\Core\Service\PermissionService; use Horde\Core\Service\PrefsService; +use Horde\Core\Service\ServiceCredentialManager; use Horde\Core\Service\VersionCheck\VersionService; use Horde\Core\Uri\RegistryRouteMapperProvider; use Horde\Core\Uri\RouteMapperProvider; @@ -288,6 +296,10 @@ public function register(Injector $injector): void HordeLdapService::class => HordeLdapServiceFactory::class, PermissionService::class => PermissionServiceFactory::class, VersionService::class => VersionServiceFactory::class, + CredentialStore::class => CredentialStoreFactory::class, + CredentialProvisioningStrategy::class => CredentialProvisioningStrategyFactory::class, + PasswordServiceAuthorizationService::class => PasswordServiceAuthorizationServiceFactory::class, + ServiceCredentialManager::class => ServiceCredentialManagerFactory::class, Tinymce::class => TinymceFactory::class, TinymcePageBinder::class => TinymcePageBinderFactory::class, EventDispatcherInterface::class => [EventDispatcherFactory::class, 'create'], diff --git a/src/Factory/ServiceAuthorizationRepositoryFactory.php b/src/Factory/ServiceAuthorizationRepositoryFactory.php new file mode 100644 index 00000000..71ddee87 --- /dev/null +++ b/src/Factory/ServiceAuthorizationRepositoryFactory.php @@ -0,0 +1,18 @@ +userId; + } + + public function providerId(): string + { + return $this->providerId; + } + + public function grant(): ?TokenGrant + { + return $this->grant; + } +} diff --git a/src/Service/Factory/CredentialProvisioningStrategyFactory.php b/src/Service/Factory/CredentialProvisioningStrategyFactory.php new file mode 100644 index 00000000..c4d85911 --- /dev/null +++ b/src/Service/Factory/CredentialProvisioningStrategyFactory.php @@ -0,0 +1,22 @@ + '...', 'password' => '...'] + */ + public function asStructured(): array; + + /** + * Returns credential as opaque string. + * For bearer tokens, API keys, or encoded strings. + */ + public function asOpaque(): string; + + /** + * Convenience method for HTTP Bearer header value. + */ + public function asBearerToken(): string; + + /** + * Convenience method for HTTP Basic Auth header value. + * Returns base64-encoded "username:password" + */ + public function asBasicAuth(): string; + + public function createdAt(): int; + public function updatedAt(): int; +} diff --git a/src/Service/PasswordCredentialNotFoundException.php b/src/Service/PasswordCredentialNotFoundException.php new file mode 100644 index 00000000..d06d64f0 --- /dev/null +++ b/src/Service/PasswordCredentialNotFoundException.php @@ -0,0 +1,47 @@ +identifier() + ); + } + parent::__construct($message, $code, $previous); + } + + public function getUserId(): string + { + return $this->userId; + } + + public function getProviderId(): string + { + return $this->providerId; + } + + public function getPurpose(): ServicePurpose + { + return $this->purpose; + } +} diff --git a/src/Service/PasswordServiceAuthorizationService.php b/src/Service/PasswordServiceAuthorizationService.php new file mode 100644 index 00000000..be18accf --- /dev/null +++ b/src/Service/PasswordServiceAuthorizationService.php @@ -0,0 +1,72 @@ +getAccessToken(). + * + * @throws ServiceNotAuthorizedException when !isSatisfied() + * @throws OAuthTokenRefreshException when the grant refresh fails + */ + public function getAccessToken(): string; +} diff --git a/src/Service/ServiceAuthorizationRepository.php b/src/Service/ServiceAuthorizationRepository.php new file mode 100644 index 00000000..4f3f943a --- /dev/null +++ b/src/Service/ServiceAuthorizationRepository.php @@ -0,0 +1,26 @@ +type === CredentialType::OAuth + ? $this->credential->getAccessToken() + : null; + } + + /** + * Convenience accessor for password credential. + */ + public function asPassword(): ?PasswordCredential + { + return $this->type === CredentialType::Password + ? $this->credential + : null; + } +} diff --git a/src/Service/ServiceNotAuthorizedException.php b/src/Service/ServiceNotAuthorizedException.php new file mode 100644 index 00000000..c4ad3a69 --- /dev/null +++ b/src/Service/ServiceNotAuthorizedException.php @@ -0,0 +1,51 @@ +identifier(), + $providerId + ); + } + parent::__construct($message, $code, $previous); + } + + public function userId(): string + { + return $this->userId; + } + + public function providerId(): string + { + return $this->providerId; + } + + public function purpose(): ServicePurpose + { + return $this->purpose; + } + + /** Scopes still needed; empty ScopeSet if no grant exists at all. */ + public function missingScopes(): ScopeSet + { + return $this->missingScopes; + } +} diff --git a/src/Service/ServicePurpose.php b/src/Service/ServicePurpose.php new file mode 100644 index 00000000..006aa196 --- /dev/null +++ b/src/Service/ServicePurpose.php @@ -0,0 +1,50 @@ +identifier; + } + + public function grantStrategy(): GrantStrategy + { + return $this->grantStrategy; + } + + public function equals(self $other): bool + { + return $this->identifier === $other->identifier; + } + + /** Round-trip through OAuthFlowData persistence. */ + public function serialize(): array + { + return ['id' => $this->identifier, 'strategy' => $this->grantStrategy->value]; + } + + public static function deserialize(array $data): self + { + return new self($data['id'], GrantStrategy::from($data['strategy'])); + } +} diff --git a/src/Service/TokenGrant.php b/src/Service/TokenGrant.php new file mode 100644 index 00000000..3d1558fe --- /dev/null +++ b/src/Service/TokenGrant.php @@ -0,0 +1,36 @@ +identifier() + ); + } + parent::__construct($message, $code, $previous); + } + + public function providerId(): string + { + return $this->providerId; + } + + public function purpose(): ServicePurpose + { + return $this->purpose; + } +}