authentication provider provisioning
This commit is contained in:
180
lib/Provider.php
180
lib/Provider.php
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user