authentication provider provisioning

This commit is contained in:
root
2025-12-22 17:07:07 -05:00
parent 52ff8a8314
commit 42a5897d29

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace KTXM\AuthenticationProviderOidc; namespace KTXM\AuthenticationProviderOidc;
use KTXC\Service\UserService;
use KTXF\Security\Authentication\AuthenticationProviderAbstract; use KTXF\Security\Authentication\AuthenticationProviderAbstract;
use KTXF\Security\Authentication\ProviderContext; use KTXF\Security\Authentication\ProviderContext;
use KTXF\Security\Authentication\ProviderResult; use KTXF\Security\Authentication\ProviderResult;
@@ -18,6 +19,7 @@ class Provider extends AuthenticationProviderAbstract
{ {
public function __construct( public function __construct(
private readonly OidcClient $client, private readonly OidcClient $client,
private readonly UserService $userService,
) { } ) { }
// ========================================================================= // =========================================================================
@@ -114,13 +116,21 @@ class Provider extends AuthenticationProviderAbstract
); );
} }
// Provision/update user directly
$user = $this->provisionUser($result, $config);
if (!$user) {
return ProviderResult::failed(
ProviderResult::ERROR_INTERNAL,
'Failed to provision user from OIDC identity'
);
}
// Return success with user identifier
return ProviderResult::success( return ProviderResult::success(
[ [
'provider' => $this->identifier(), 'user_identifier' => $user['uid'],
'subject' => $result['sub'] ?? null, 'user_identity' => $user['identity'],
'email' => $result['email'] ?? null,
'name' => $result['name'] ?? null,
'attributes' => $result['attributes'] ?? [],
], ],
[ [
'return_url' => $context->getMeta('return_url'), 'return_url' => $context->getMeta('return_url'),
@@ -135,23 +145,165 @@ class Provider extends AuthenticationProviderAbstract
} }
// ========================================================================= // =========================================================================
// Attribute Helpers // Provisioning Helpers
// ========================================================================= // =========================================================================
public function getAttributes(array $identity): array /**
* Provision or update user from OIDC identity
*/
private function provisionUser(array $oidcData, array $config): ?array
{ {
return $identity['attributes'] ?? []; $subject = $oidcData['sub'] ?? null;
$email = $oidcData['email'] ?? null;
if (!$subject || !$email) {
return null;
} }
public function mapAttributes(array $attributes, array $mapping): array // Check if user exists by provider subject
$user = $this->userService->fetchByProviderSubject($this->identifier(), $subject);
if ($user) {
// Update existing user
return $this->updateUser($user, $oidcData, $config);
}
// Check if user exists by email
$user = $this->userService->fetchByIdentityRaw($email);
if ($user) {
// Link existing user to this OIDC provider
return $this->linkUser($user, $oidcData, $config);
}
// Create new user
return $this->createUser($oidcData, $config);
}
/**
* Create new user from OIDC data
*/
private function createUser(array $oidcData, array $config): array
{ {
$mapped = []; $managedFields = $this->getManagedFields($config);
foreach ($mapping as $sourceKey => $targetKey) {
if (isset($attributes[$sourceKey])) { $userData = [
$mapped[$targetKey] = $attributes[$sourceKey]; 'identity' => $oidcData['email'],
'label' => $oidcData['name'] ?? $oidcData['email'],
'enabled' => true,
'provider' => $this->identifier(),
'provider_subject' => $oidcData['sub'],
'provider_synced_at' => time(),
'provider_managed_fields' => $managedFields,
'profile' => [],
];
// Map OIDC attributes to profile
$mapping = [
'given_name' => 'name_given',
'family_name' => 'name_family',
'picture' => 'avatar',
'email' => 'email',
'phone_number' => 'phone',
];
foreach ($mapping as $oidcField => $profileField) {
if (isset($oidcData[$oidcField])) {
$userData['profile'][$profileField] = $oidcData[$oidcField];
} }
} }
return $mapped;
return $this->userService->createUser($userData);
}
/**
* Update existing user with OIDC data
*/
private function updateUser(array $user, array $oidcData, array $config): array
{
$managedFields = $this->getManagedFields($config);
// Update provider sync metadata
$this->userService->updateUser($user['uid'], [
'provider_synced_at' => time(),
'provider_managed_fields' => $managedFields,
]);
// Update label if provided
if (isset($oidcData['name'])) {
$this->userService->updateUser($user['uid'], [
'label' => $oidcData['name'],
]);
}
// Update profile fields (only managed ones will be updated due to storeProfile filtering)
$profileUpdates = [];
$mapping = [
'given_name' => 'name_given',
'family_name' => 'name_family',
'picture' => 'avatar',
];
foreach ($mapping as $oidcField => $profileField) {
if (isset($oidcData[$oidcField]) && in_array($profileField, $managedFields)) {
$profileUpdates[$profileField] = $oidcData[$oidcField];
}
}
if (!empty($profileUpdates)) {
// Use updateUser with profile. notation since these are managed fields
$updates = [];
foreach ($profileUpdates as $field => $value) {
$updates["profile.{$field}"] = $value;
}
$this->userService->updateUser($user['uid'], $updates);
}
return $this->userService->fetchByIdentifier($user['uid']);
}
/**
* Link existing local user to OIDC provider
*/
private function linkUser(array $user, array $oidcData, array $config): array
{
$managedFields = $this->getManagedFields($config);
// Link existing local user to OIDC provider
$this->userService->updateUser($user['uid'], [
'provider' => $this->identifier(),
'provider_subject' => $oidcData['sub'],
'provider_synced_at' => time(),
'provider_managed_fields' => $managedFields,
]);
// Then update with OIDC data
return $this->updateUser(
$this->userService->fetchByIdentifier($user['uid']),
$oidcData,
$config
);
}
/**
* Get default managed fields for OIDC provider
*/
private function managedFields(): array
{
return [
'name_given',
'name_family',
'avatar',
];
}
/**
* Get managed fields from config or use defaults
*/
private function getManagedFields(array $config): array
{
// Allow configuration to override default managed fields
return $config['managed_fields'] ?? $this->managedFields();
} }
} }