resource provider and service improvements

This commit is contained in:
root
2026-01-04 21:43:20 -05:00
parent 8f35442335
commit 965d839a8c
9 changed files with 64 additions and 127 deletions

View File

@@ -6,7 +6,9 @@ namespace KTXC\Service;
use KTXC\Http\Request\Request; use KTXC\Http\Request\Request;
use KTXC\Models\Identity\User; use KTXC\Models\Identity\User;
use KTXC\Resource\ProviderManager;
use KTXC\SessionTenant; use KTXC\SessionTenant;
use KTXF\Security\Authentication\AuthenticationProviderInterface;
/** /**
* Security Service * Security Service
@@ -21,9 +23,10 @@ class SecurityService
private string $securityCode; private string $securityCode;
public function __construct( public function __construct(
private readonly SessionTenant $sessionTenant,
private readonly TokenService $tokenService, private readonly TokenService $tokenService,
private readonly UserAccountsService $userService, private readonly UserAccountsService $userService,
private readonly SessionTenant $sessionTenant private readonly ProviderManager $providerManager,
) { ) {
$this->securityCode = $this->sessionTenant->configuration()->security()->code(); $this->securityCode = $this->sessionTenant->configuration()->security()->code();
} }
@@ -55,7 +58,7 @@ class SecurityService
if ($decoded !== false) { if ($decoded !== false) {
[$identity, $secret] = array_pad(explode(':', $decoded, 2), 2, null); [$identity, $secret] = array_pad(explode(':', $decoded, 2), 2, null);
if ($identity !== null && $secret !== null) { if ($identity !== null && $secret !== null) {
return $this->authenticateBasicHeader($identity, $secret); return $this->authenticateBasic($identity, $secret);
} }
} }
} }
@@ -98,98 +101,38 @@ class SecurityService
* Authenticate HTTP Basic header (for API access) * Authenticate HTTP Basic header (for API access)
* Note: This is for request authentication, not login * Note: This is for request authentication, not login
*/ */
private function authenticateBasicHeader(string $identity, string $credentials): ?User private function authenticateBasic(string $identity, string $credentials): ?User
{ {
// For Basic auth headers, we need to validate against the provider // For Basic auth headers, we need to validate against the provider
// This is a simplified flow for API access // This is a simplified flow for API access
$provider = $this->providerRegistry->resolve('default'); $providers = $this->providerManager->providers(AuthenticationProviderInterface::TYPE_AUTHENTICATION);
if ($provider === null) { if ($providers === []) {
return null; return null;
} }
$result = $provider->authenticate($identity, $credentials); foreach ($providers as $provider) {
if (!$result->isSuccess()) { if ($provider instanceof AuthenticationProviderInterface === false) {
return null; continue;
}
if ($provider->method() !== AuthenticationProviderInterface::METHOD_CREDENTIAL) {
continue;
}
$context = new \KTXF\Security\Authentication\ProviderContext(
tenantId: $this->sessionTenant->identifier(),
userIdentity: $identity,
);
$result = $provider->verify($context, $credentials);
if ($result->isSuccess()) {
break;
}
} }
return $this->getUserByIdentity($identity); if (isset($result) && $result->isSuccess()) {
} return $this->userService->fetchByIdentity($identity);
// =========================================================================
// Token Operations (delegated to AuthenticationManager for new flows)
// These are kept for backwards compatibility during transition
// =========================================================================
/**
* @deprecated Use AuthenticationManager::createTokens() instead
*/
public function createAccessToken(array $payload): string
{
return $this->tokenService->createToken($payload, $this->securityCode, 900);
}
/**
* @deprecated Use AuthenticationManager::createTokens() instead
*/
public function createRefreshToken(array $payload): string
{
$refreshPayload = [
'tenant' => $payload['tenant'] ?? null,
'identifier' => $payload['identifier'],
'identity' => $payload['identity'],
'type' => 'refresh'
];
return $this->tokenService->createToken($refreshPayload, $this->securityCode, 604800);
}
/**
* @deprecated Use AuthenticationManager::refreshAccessToken() instead
*/
public function validateRefreshToken(string $refreshToken): ?User
{
$payload = $this->tokenService->validateToken($refreshToken, $this->securityCode);
if (!$payload) {
return null;
} }
if (!isset($payload['type']) || $payload['type'] !== 'refresh') { return null;
return null;
}
$identifier = $payload['identifier'] ?? null;
if (!$identifier || $this->providerRegistry->validateUser($identifier) === false) {
return null;
}
$user = new User();
$user->populate([
'identifier' => $payload['identifier'],
'identity' => $payload['identity'],
'tenant' => $payload['tenant'] ?? null,
], 'jwt');
return $user;
}
/**
* @deprecated Use AuthenticationManager::logout() instead
*/
public function logout(?string $jti = null, ?int $exp = null): void
{
if ($jti !== null) {
$expiresAt = $exp ?? (time() + 86400);
$this->tokenService->blacklist($jti, $expiresAt);
}
}
/**
* @deprecated Use AuthenticationManager::logoutAll() instead
*/
public function logoutAllDevices(string $identity): void
{
$this->tokenService->blacklistUserTokensBefore($identity, time());
} }
/** /**

View File

@@ -26,8 +26,6 @@ namespace KTXF\Mail\Provider;
*/ */
interface ProviderServiceDiscoverInterface extends ProviderBaseInterface { interface ProviderServiceDiscoverInterface extends ProviderBaseInterface {
public const CAPABILITY_SERVICE_DISCOVER = 'ServiceDiscover';
/** /**
* Discover service configuration * Discover service configuration
* *

View File

@@ -27,6 +27,8 @@ use KTXF\Resource\Provider\ResourceProviderServiceMutateInterface;
*/ */
interface ProviderServiceMutateInterface extends ProviderBaseInterface, ResourceProviderServiceMutateInterface { interface ProviderServiceMutateInterface extends ProviderBaseInterface, ResourceProviderServiceMutateInterface {
public const JSON_TYPE = ProviderBaseInterface::JSON_TYPE;
// Methods inherited from ResourceProviderServiceMutateInterface // Methods inherited from ResourceProviderServiceMutateInterface
// Implementations should return/accept ServiceMutableInterface instances // Implementations should return/accept ServiceMutableInterface instances

View File

@@ -27,8 +27,6 @@ use KTXF\Mail\Service\ServiceBaseInterface;
*/ */
interface ProviderServiceTestInterface extends ProviderBaseInterface { interface ProviderServiceTestInterface extends ProviderBaseInterface {
public const CAPABILITY_SERVICE_TEST = 'ServiceTest';
/** /**
* Test a service connection * Test a service connection
* *

View File

@@ -34,31 +34,28 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface {
public const CAPABILITY_COLLECTION_LIST_SORT = 'CollectionListSort'; public const CAPABILITY_COLLECTION_LIST_SORT = 'CollectionListSort';
public const CAPABILITY_COLLECTION_EXTANT = 'CollectionExtant'; public const CAPABILITY_COLLECTION_EXTANT = 'CollectionExtant';
public const CAPABILITY_COLLECTION_FETCH = 'CollectionFetch'; public const CAPABILITY_COLLECTION_FETCH = 'CollectionFetch';
// Message capabilities // Message capabilities
public const CAPABILITY_MESSAGE_LIST = 'MessageList'; public const CAPABILITY_ENTITY_LIST = 'EntityList';
public const CAPABILITY_MESSAGE_LIST_FILTER = 'MessageListFilter'; public const CAPABILITY_ENTITY_LIST_FILTER = 'EntityListFilter';
public const CAPABILITY_MESSAGE_LIST_SORT = 'MessageListSort'; public const CAPABILITY_ENTITY_LIST_SORT = 'EntityListSort';
public const CAPABILITY_MESSAGE_LIST_RANGE = 'MessageListRange'; public const CAPABILITY_ENTITY_LIST_RANGE = 'EntityListRange';
public const CAPABILITY_MESSAGE_DELTA = 'MessageDelta'; public const CAPABILITY_ENTITY_DELTA = 'EntityDelta';
public const CAPABILITY_MESSAGE_EXTANT = 'MessageExtant'; public const CAPABILITY_ENTITY_EXTANT = 'EntityExtant';
public const CAPABILITY_MESSAGE_FETCH = 'MessageFetch'; public const CAPABILITY_ENTITY_FETCH = 'EntityFetch';
// Filter capabilities // Filter capabilities
public const CAPABILITY_FILTER_ID = 'FilterId'; public const CAPABILITY_FILTER_ID = 'id';
public const CAPABILITY_FILTER_SUBJECT = 'FilterSubject'; public const CAPABILITY_FILTER_SUBJECT = 'subject';
public const CAPABILITY_FILTER_FROM = 'FilterFrom'; public const CAPABILITY_FILTER_FROM = 'from';
public const CAPABILITY_FILTER_TO = 'FilterTo'; public const CAPABILITY_FILTER_TO = 'to';
public const CAPABILITY_FILTER_DATE = 'FilterDate'; public const CAPABILITY_FILTER_DATE = 'date';
public const CAPABILITY_FILTER_FLAG = 'FilterFlag'; public const CAPABILITY_FILTER_FLAG = 'flag';
public const CAPABILITY_FILTER_SIZE = 'FilterSize'; public const CAPABILITY_FILTER_SIZE = 'size';
public const CAPABILITY_FILTER_BODY = 'FilterBody'; public const CAPABILITY_FILTER_BODY = 'body';
// Sort capabilities // Sort capabilities
public const CAPABILITY_SORT_DATE = 'SortDate'; public const CAPABILITY_SORT_DATE = 'date';
public const CAPABILITY_SORT_SUBJECT = 'SortSubject'; public const CAPABILITY_SORT_SUBJECT = 'subject';
public const CAPABILITY_SORT_FROM = 'SortFrom'; public const CAPABILITY_SORT_FROM = 'from';
public const CAPABILITY_SORT_SIZE = 'SortSize'; public const CAPABILITY_SORT_SIZE = 'size';
public const JSON_TYPE = 'mail.service'; public const JSON_TYPE = 'mail.service';
public const JSON_PROPERTY_PRIMARY_ADDRESS = 'primaryAddress'; public const JSON_PROPERTY_PRIMARY_ADDRESS = 'primaryAddress';

View File

@@ -22,12 +22,12 @@ use KTXF\Mail\Entity\IMessageMutable;
*/ */
interface ServiceMessageMutableInterface extends ServiceBaseInterface { interface ServiceMessageMutableInterface extends ServiceBaseInterface {
public const CAPABILITY_MESSAGE_CREATE = 'MessageCreate'; public const CAPABILITY_ENTITY_CREATE = 'EntityCreate';
public const CAPABILITY_MESSAGE_MODIFY = 'MessageModify'; public const CAPABILITY_ENTITY_MODIFY = 'EntityModify';
public const CAPABILITY_MESSAGE_DESTROY = 'MessageDestroy'; public const CAPABILITY_ENTITY_DESTROY = 'EntityDestroy';
public const CAPABILITY_MESSAGE_COPY = 'MessageCopy'; public const CAPABILITY_ENTITY_COPY = 'EntityCopy';
public const CAPABILITY_MESSAGE_MOVE = 'MessageMove'; public const CAPABILITY_ENTITY_MOVE = 'EntityMove';
public const CAPABILITY_MESSAGE_FLAG = 'MessageFlag'; public const CAPABILITY_ENTITY_FLAG = 'EntityFlag';
/** /**
* Creates a fresh message instance for composition * Creates a fresh message instance for composition

View File

@@ -11,9 +11,14 @@ use KTXF\Json\JsonSerializable;
*/ */
interface ResourceProviderBaseInterface extends ProviderInterface, JsonSerializable interface ResourceProviderBaseInterface extends ProviderInterface, JsonSerializable
{ {
public const CAPABILITY_SERVICE_LIST = 'ServiceList'; public const CAPABILITY_SERVICE_LIST = 'ServiceList';
public const CAPABILITY_SERVICE_FETCH = 'ServiceFetch'; public const CAPABILITY_SERVICE_FETCH = 'ServiceFetch';
public const CAPABILITY_SERVICE_EXTANT = 'ServiceExtant'; public const CAPABILITY_SERVICE_EXTANT = 'ServiceExtant';
public const CAPABILITY_SERVICE_CREATE = 'ServiceCreate';
public const CAPABILITY_SERVICE_MODIFY = 'ServiceModify';
public const CAPABILITY_SERVICE_DESTROY = 'ServiceDestroy';
public const CAPABILITY_SERVICE_DISCOVER = 'ServiceDiscover';
public const CAPABILITY_SERVICE_TEST = 'ServiceTest';
public const JSON_TYPE = 'resource.provider'; public const JSON_TYPE = 'resource.provider';
public const JSON_PROPERTY_TYPE = '@type'; public const JSON_PROPERTY_TYPE = '@type';

View File

@@ -13,37 +13,32 @@ use KTXF\Json\JsonDeserializable;
interface ResourceProviderServiceMutateInterface extends ResourceProviderBaseInterface, JsonDeserializable { interface ResourceProviderServiceMutateInterface extends ResourceProviderBaseInterface, JsonDeserializable {
public const CAPABILITY_SERVICE_FRESH = 'ServiceFresh';
public const CAPABILITY_SERVICE_CREATE = 'ServiceCreate';
public const CAPABILITY_SERVICE_UPDATE = 'ServiceUpdate';
public const CAPABILITY_SERVICE_DESTROY = 'ServiceDestroy';
/** /**
* construct and new blank service instance * construct and new blank service instance
* *
* @since 2025.05.01 * @since 2025.05.01
*/ */
public function serviceFresh(string $tenantId, ?string $userId): ResourceServiceMutateInterface; public function serviceFresh(): ResourceServiceMutateInterface;
/** /**
* create a service configuration for a specific user * create a service configuration for a specific user
* *
* @since 2025.05.01 * @since 2025.05.01
*/ */
public function serviceCreate(string $tenantId, ?string $userId, ResourceServiceMutateInterface $service): string; public function serviceCreate(string $tenantId, string $userId, ResourceServiceMutateInterface $service): string;
/** /**
* modify a service configuration for a specific user * modify a service configuration for a specific user
* *
* @since 2025.05.01 * @since 2025.05.01
*/ */
public function serviceModify(string $tenantId, ?string $userId, ResourceServiceMutateInterface $service): string; public function serviceModify(string $tenantId, string $userId, ResourceServiceMutateInterface $service): string;
/** /**
* delete a service configuration for a specific user * delete a service configuration for a specific user
* *
* @since 2025.05.01 * @since 2025.05.01
*/ */
public function serviceDestroy(string $tenantId, ?string $userId, ResourceServiceMutateInterface $service): bool; public function serviceDestroy(string $tenantId, string $userId, ResourceServiceMutateInterface $service): bool;
} }

View File

@@ -11,7 +11,6 @@ interface ResourceServiceBaseInterface extends JsonSerializable {
// JSON Constants // JSON Constants
public const JSON_TYPE = 'resource.service'; public const JSON_TYPE = 'resource.service';
public const JSON_PROPERTY_TYPE = '@type'; public const JSON_PROPERTY_TYPE = '@type';
public const JSON_PROPERTY_PROVIDER = 'provider';
public const JSON_PROPERTY_ID = 'id'; public const JSON_PROPERTY_ID = 'id';
public const JSON_PROPERTY_LABEL = 'label'; public const JSON_PROPERTY_LABEL = 'label';
public const JSON_PROPERTY_ENABLED = 'enabled'; public const JSON_PROPERTY_ENABLED = 'enabled';