diff --git a/README.md b/README.md index 9bf7c263..811b20ed 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,40 @@ This app can stop matching users (when a user search is performed in Nextcloud) ], ``` +### Optional: Enable support for nested and fallback claim mappings + +By default, claim mapping in this app uses **flat attribute keys** like `email`, `name`, `custom.nickname`, etc. +However, some Identity Providers return **structured tokens** (nested JSON), and mapping such claims requires dot-notation (e.g. `custom.nickname` → `{ "custom": { "nickname": "value" } }`). + +Additionally, you may want to define **fallbacks**, in case a preferred claim is missing, using the `|` separator. + +#### Example + +``` +custom.nickname | profile.name | name +``` + +This will return the first non-empty string from the token in the order defined. + + +#### Enabling this behavior (optional) + +To enable support for dot-notation and fallback claims for a specific provider, set the following configuration flag via the Nextcloud command line: + +```bash +php occ user_oidc:provider --resolve-nested-claims=1 +``` + +To disable again: + +```bash +php occ user_oidc:provider --resolve-nested-claims=0 +``` + +This setting is also available in the web interface when configuring a provider. +This setting is **disabled by default** to ensure full backward compatibility with existing configurations and flat token structures. + + ## Building the app Requirements for building: diff --git a/lib/Command/UpsertProvider.php b/lib/Command/UpsertProvider.php index d2fc286b..0182e070 100644 --- a/lib/Command/UpsertProvider.php +++ b/lib/Command/UpsertProvider.php @@ -143,6 +143,12 @@ class UpsertProvider extends Base { 'shortcut' => null, 'mode' => InputOption::VALUE_REQUIRED, 'setting_key' => ProviderService::SETTING_MAPPING_GROUPS, 'description' => 'Attribute mapping of the groups', ], + 'resolve-nested-claims' => [ + 'shortcut' => null, + 'mode' => InputOption::VALUE_REQUIRED, + 'setting_key' => ProviderService::SETTING_RESOLVE_NESTED_AND_FALLBACK_CLAIMS_MAPPING, + 'description' => 'Enable support for dot-separated and fallback claim mappings (e.g. "a.b | c.d | e"). 1 to enable, 0 to disable (default)', + ], ]; public function __construct( diff --git a/lib/Controller/LoginController.php b/lib/Controller/LoginController.php index 6c428c83..7ba5e612 100644 --- a/lib/Controller/LoginController.php +++ b/lib/Controller/LoginController.php @@ -199,6 +199,7 @@ public function login(int $providerId, ?string $redirectUrl = null) { 'userinfo' => [], ]; + $resolveNestedClaims = $this->providerService->getSetting($providerId, ProviderService::SETTING_RESOLVE_NESTED_AND_FALLBACK_CLAIMS_MAPPING, '0') === '1'; // by default: default claims are ENABLED // default claims are historically for quota, email, displayName and groups $isDefaultClaimsEnabled = !isset($oidcSystemConfig['enable_default_claims']) || $oidcSystemConfig['enable_default_claims'] !== false; @@ -218,7 +219,20 @@ public function login(int $providerId, ?string $redirectUrl = null) { $displaynameAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_DISPLAYNAME); $quotaAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_QUOTA); $groupsAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_GROUPS); - foreach ([$emailAttribute, $displaynameAttribute, $quotaAttribute, $groupsAttribute] as $claim) { + $rawClaims = [$emailAttribute, $displaynameAttribute, $quotaAttribute, $groupsAttribute]; + + if ($resolveNestedClaims) { + $claimSet = []; + foreach ($rawClaims as $claim) { + if ($claim !== '') { + $first = trim(explode('|', $claim)[0]); + $claimSet[$first] = true; + } + } + $rawClaims = array_keys($claimSet); + } + + foreach ($rawClaims as $claim) { if ($claim !== '') { $claims['id_token'][$claim] = null; $claims['userinfo'][$claim] = null; @@ -227,8 +241,12 @@ public function login(int $providerId, ?string $redirectUrl = null) { } if ($uidAttribute !== 'sub') { - $claims['id_token'][$uidAttribute] = ['essential' => true]; - $claims['userinfo'][$uidAttribute] = ['essential' => true]; + $uidAttributeToRequest = $uidAttribute; + if ($resolveNestedClaims) { + $uidAttributeToRequest = trim(explode('|', $uidAttribute)[0]); + } + $claims['id_token'][$uidAttributeToRequest] = ['essential' => true]; + $claims['userinfo'][$uidAttributeToRequest] = ['essential' => true]; } $extraClaimsString = $this->providerService->getSetting($providerId, ProviderService::SETTING_EXTRA_CLAIMS, ''); @@ -459,7 +477,8 @@ public function code(string $state = '', string $code = '', string $scope = '', // get user ID attribute $uidAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_UID, 'sub'); - $userId = $idTokenPayload->{$uidAttribute} ?? null; + $userId = $this->provisioningService->getClaimValue($idTokenPayload, $uidAttribute, $providerId); + if ($userId === null) { $message = $this->l10n->t('Failed to provision the user'); return $this->build403TemplateResponse($message, Http::STATUS_BAD_REQUEST, ['reason' => 'failed to provision user']); diff --git a/lib/Service/ProviderService.php b/lib/Service/ProviderService.php index ae2d41b2..6ecac6ff 100644 --- a/lib/Service/ProviderService.php +++ b/lib/Service/ProviderService.php @@ -51,6 +51,7 @@ class ProviderService { public const SETTING_GROUP_PROVISIONING = 'groupProvisioning'; public const SETTING_GROUP_WHITELIST_REGEX = 'groupWhitelistRegex'; public const SETTING_RESTRICT_LOGIN_TO_GROUPS = 'restrictLoginToGroups'; + public const SETTING_RESOLVE_NESTED_AND_FALLBACK_CLAIMS_MAPPING = 'nestedAndFallbackClaims'; public const BOOLEAN_SETTINGS_DEFAULT_VALUES = [ self::SETTING_GROUP_PROVISIONING => false, @@ -60,6 +61,7 @@ class ProviderService { self::SETTING_CHECK_BEARER => false, self::SETTING_SEND_ID_TOKEN_HINT => false, self::SETTING_RESTRICT_LOGIN_TO_GROUPS => false, + self::SETTING_RESOLVE_NESTED_AND_FALLBACK_CLAIMS_MAPPING => false, ]; public function __construct( @@ -168,6 +170,7 @@ private function getSupportedSettings(): array { self::SETTING_GROUP_PROVISIONING, self::SETTING_GROUP_WHITELIST_REGEX, self::SETTING_RESTRICT_LOGIN_TO_GROUPS, + self::SETTING_RESOLVE_NESTED_AND_FALLBACK_CLAIMS_MAPPING, ]; } diff --git a/lib/Service/ProvisioningService.php b/lib/Service/ProvisioningService.php index a853d95d..8dddd616 100644 --- a/lib/Service/ProvisioningService.php +++ b/lib/Service/ProvisioningService.php @@ -58,6 +58,52 @@ public function hasOidcUserProvisitioned(string $userId): bool { return false; } + /** + * Resolves a claim path like "custom.nickname" or multiple alternatives separated by "|". + * Returns the first found string value, or null if none could be resolved. + */ + public function getClaimValue(object|array $tokenPayload, string $claimPath, int $providerId): mixed { + if ($claimPath === '') { + return null; + } + + // Check config if dot-notation resolution is enabled + $resolveDot = $this->providerService->getSetting($providerId, ProviderService::SETTING_RESOLVE_NESTED_AND_FALLBACK_CLAIMS_MAPPING, '0') === '1'; + + if (!$resolveDot) { + // fallback to simple access + if (is_object($tokenPayload) && property_exists($tokenPayload, $claimPath)) { + return $tokenPayload->{$claimPath}; + } elseif (is_array($tokenPayload) && array_key_exists($claimPath, $tokenPayload)) { + return $tokenPayload[$claimPath]; + } + return null; + } + + // Support alternatives separated by "|" + $alternatives = explode('|', $claimPath); + + foreach ($alternatives as $altPath) { + $parts = explode('.', trim($altPath)); + $value = $tokenPayload; + + foreach ($parts as $part) { + if (is_object($value) && property_exists($value, $part)) { + $value = $value->{$part}; + } elseif (is_array($value) && array_key_exists($part, $value)) { + $value = $value[$part]; + } else { + continue 2; + } + } + + if (is_string($value)) { + return $value; + } + } + + return null; + } /** * @param string $tokenUserId * @param int $providerId @@ -72,64 +118,64 @@ public function provisionUser(string $tokenUserId, int $providerId, object $idTo // get name/email/quota information from the token itself $emailAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_EMAIL, 'email'); - $email = $idTokenPayload->{$emailAttribute} ?? null; + $email = $this->getClaimValue($idTokenPayload, $emailAttribute, $providerId);//$idTokenPayload->{$emailAttribute} ?? null; $displaynameAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_DISPLAYNAME, 'name'); - $userName = $idTokenPayload->{$displaynameAttribute} ?? null; + $userName = $this->getClaimValue($idTokenPayload, $displaynameAttribute, $providerId);//$idTokenPayload->{$displaynameAttribute} ?? null; $quotaAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_QUOTA, 'quota'); - $quota = $idTokenPayload->{$quotaAttribute} ?? null; + $quota = $this->getClaimValue($idTokenPayload, $quotaAttribute, $providerId);//$idTokenPayload->{$quotaAttribute} ?? null; $languageAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_LANGUAGE, 'language'); - $language = $idTokenPayload->{$languageAttribute} ?? null; + $language = $this->getClaimValue($idTokenPayload, $languageAttribute, $providerId);//$idTokenPayload->{$languageAttribute} ?? null; $genderAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_GENDER, 'gender'); - $gender = $idTokenPayload->{$genderAttribute} ?? null; + $gender = $this->getClaimValue($idTokenPayload, $genderAttribute, $providerId);//$idTokenPayload->{$genderAttribute} ?? null; $addressAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_ADDRESS, 'address'); - $address = $idTokenPayload->{$addressAttribute} ?? null; + $address = $this->getClaimValue($idTokenPayload, $addressAttribute, $providerId);//$idTokenPayload->{$addressAttribute} ?? null; $postalcodeAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_POSTALCODE, 'postal_code'); - $postalcode = $idTokenPayload->{$postalcodeAttribute} ?? null; + $postalcode = $this->getClaimValue($idTokenPayload, $postalcodeAttribute, $providerId);//$idTokenPayload->{$postalcodeAttribute} ?? null; $streetAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_STREETADDRESS, 'street_address'); - $street = $idTokenPayload->{$streetAttribute} ?? null; + $street = $this->getClaimValue($idTokenPayload, $streetAttribute, $providerId);//$idTokenPayload->{$streetAttribute} ?? null; $localityAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_LOCALITY, 'locality'); - $locality = $idTokenPayload->{$localityAttribute} ?? null; + $locality = $this->getClaimValue($idTokenPayload, $localityAttribute, $providerId);//$idTokenPayload->{$localityAttribute} ?? null; $regionAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_REGION, 'region'); - $region = $idTokenPayload->{$regionAttribute} ?? null; + $region = $this->getClaimValue($idTokenPayload, $regionAttribute, $providerId);//$idTokenPayload->{$regionAttribute} ?? null; $countryAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_COUNTRY, 'country'); - $country = $idTokenPayload->{$countryAttribute} ?? null; + $country = $this->getClaimValue($idTokenPayload, $countryAttribute, $providerId);//$idTokenPayload->{$countryAttribute} ?? null; $websiteAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_WEBSITE, 'website'); - $website = $idTokenPayload->{$websiteAttribute} ?? null; + $website = $this->getClaimValue($idTokenPayload, $websiteAttribute, $providerId);//$idTokenPayload->{$websiteAttribute} ?? null; $avatarAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_AVATAR, 'avatar'); - $avatar = $idTokenPayload->{$avatarAttribute} ?? null; + $avatar = $this->getClaimValue($idTokenPayload, $avatarAttribute, $providerId);//$idTokenPayload->{$avatarAttribute} ?? null; $phoneAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_PHONE, 'phone_number'); - $phone = $idTokenPayload->{$phoneAttribute} ?? null; + $phone = $this->getClaimValue($idTokenPayload, $phoneAttribute, $providerId);//$idTokenPayload->{$phoneAttribute} ?? null; $twitterAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_TWITTER, 'twitter'); - $twitter = $idTokenPayload->{$twitterAttribute} ?? null; + $twitter = $this->getClaimValue($idTokenPayload, $twitterAttribute, $providerId);//$idTokenPayload->{$twitterAttribute} ?? null; $fediverseAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_FEDIVERSE, 'fediverse'); - $fediverse = $idTokenPayload->{$fediverseAttribute} ?? null; + $fediverse = $this->getClaimValue($idTokenPayload, $fediverseAttribute, $providerId);//$idTokenPayload->{$fediverseAttribute} ?? null; $organisationAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_ORGANISATION, 'organisation'); - $organisation = $idTokenPayload->{$organisationAttribute} ?? null; + $organisation = $this->getClaimValue($idTokenPayload, $organisationAttribute, $providerId);//$idTokenPayload->{$organisationAttribute} ?? null; $roleAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_ROLE, 'role'); - $role = $idTokenPayload->{$roleAttribute} ?? null; + $role = $this->getClaimValue($idTokenPayload, $roleAttribute, $providerId);//$idTokenPayload->{$roleAttribute} ?? null; $headlineAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_HEADLINE, 'headline'); - $headline = $idTokenPayload->{$headlineAttribute} ?? null; + $headline = $this->getClaimValue($idTokenPayload, $headlineAttribute, $providerId);//$idTokenPayload->{$headlineAttribute} ?? null; $biographyAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_BIOGRAPHY, 'biography'); - $biography = $idTokenPayload->{$biographyAttribute} ?? null; + $biography = $this->getClaimValue($idTokenPayload, $biographyAttribute, $providerId);//$idTokenPayload->{$biographyAttribute} ?? null; $event = new AttributeMappedEvent(ProviderService::SETTING_MAPPING_UID, $idTokenPayload, $tokenUserId); $this->eventDispatcher->dispatchTyped($event); diff --git a/lib/User/Backend.php b/lib/User/Backend.php index 2b4533bf..e7376897 100644 --- a/lib/User/Backend.php +++ b/lib/User/Backend.php @@ -17,6 +17,7 @@ use OCA\UserOIDC\Service\DiscoveryService; use OCA\UserOIDC\Service\LdapService; use OCA\UserOIDC\Service\ProviderService; +use OCA\UserOIDC\Service\ProvisioningService; use OCA\UserOIDC\User\Validator\SelfEncodedValidator; use OCA\UserOIDC\User\Validator\UserInfoValidator; use OCP\AppFramework\Db\DoesNotExistException; @@ -56,6 +57,7 @@ public function __construct( private DiscoveryService $discoveryService, private ProviderMapper $providerMapper, private ProviderService $providerService, + private ProvisioningService $provisioningService, private LdapService $ldapService, private IUserManager $userManager, ) { @@ -175,22 +177,21 @@ private function formatUserData(int $providerId, array $attributes): array { $result = ['formatted' => [], 'raw' => $attributes]; $emailAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_EMAIL, 'email'); - $result['formatted']['email'] = $attributes[$emailAttribute] ?? null; + $result['formatted']['email'] = $this->provisioningService->getClaimValue($attributes, $emailAttribute, $providerId); $displaynameAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_DISPLAYNAME, 'name'); - $result['formatted']['displayName'] = $attributes[$displaynameAttribute] ?? null; - + $result['formatted']['displayName'] = $this->provisioningService->getClaimValue($attributes, $displaynameAttribute, $providerId); $quotaAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_QUOTA, 'quota'); - $result['formatted']['quota'] = $attributes[$quotaAttribute] ?? null; + $result['formatted']['quota'] = $this->provisioningService->getClaimValue($attributes, $quotaAttribute, $providerId); if ($result['formatted']['quota'] === '') { $result['formatted']['quota'] = 'default'; } $groupsAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_GROUPS, 'groups'); - $result['formatted']['groups'] = $attributes[$groupsAttribute] ?? null; + $result['formatted']['groups'] = $this->provisioningService->getClaimValue($attributes, $groupsAttribute, $providerId); $uidAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_UID, 'sub'); - $result['formatted']['uid'] = $attributes[$uidAttribute] ?? null; + $result['formatted']['uid'] = $this->provisioningService->getClaimValue($attributes, $uidAttribute, $providerId); return $result; } diff --git a/lib/User/Validator/SelfEncodedValidator.php b/lib/User/Validator/SelfEncodedValidator.php index ebcd3e2c..400751d0 100644 --- a/lib/User/Validator/SelfEncodedValidator.php +++ b/lib/User/Validator/SelfEncodedValidator.php @@ -12,6 +12,7 @@ use OCA\UserOIDC\Db\Provider; use OCA\UserOIDC\Service\DiscoveryService; use OCA\UserOIDC\Service\ProviderService; +use OCA\UserOIDC\Service\ProvisioningService; use OCA\UserOIDC\User\Provisioning\SelfEncodedTokenProvisioning; use OCA\UserOIDC\Vendor\Firebase\JWT\JWT; use OCP\AppFramework\Utility\ITimeFactory; @@ -23,6 +24,7 @@ class SelfEncodedValidator implements IBearerTokenValidator { public function __construct( private DiscoveryService $discoveryService, + private ProvisioningService $provisioningService, private LoggerInterface $logger, private ITimeFactory $timeFactory, private IConfig $config, @@ -32,7 +34,8 @@ public function __construct( public function isValidBearerToken(Provider $provider, string $bearerToken): ?string { /** @var ProviderService $providerService */ $providerService = \OC::$server->get(ProviderService::class); - $uidAttribute = $providerService->getSetting($provider->getId(), ProviderService::SETTING_MAPPING_UID, ProviderService::SETTING_MAPPING_UID_DEFAULT); + $providerId = $provider->getId(); + $uidAttribute = $providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_UID, ProviderService::SETTING_MAPPING_UID_DEFAULT); // try to decode the bearer token JWT::$leeway = 60; @@ -73,11 +76,8 @@ public function isValidBearerToken(Provider $provider, string $bearerToken): ?st } // find the user ID - if (!isset($payload->{$uidAttribute})) { - return null; - } - - return $payload->{$uidAttribute}; + $uid = $this->provisioningService->getClaimValue($payload, $uidAttribute, $providerId); + return $uid ?: null; } public function getProvisioningStrategy(): string { diff --git a/lib/User/Validator/UserInfoValidator.php b/lib/User/Validator/UserInfoValidator.php index f261b4ec..fd36a08c 100644 --- a/lib/User/Validator/UserInfoValidator.php +++ b/lib/User/Validator/UserInfoValidator.php @@ -12,23 +12,24 @@ use OCA\UserOIDC\Db\Provider; use OCA\UserOIDC\Service\OIDCService; use OCA\UserOIDC\Service\ProviderService; +use OCA\UserOIDC\Service\ProvisioningService; class UserInfoValidator implements IBearerTokenValidator { public function __construct( private OIDCService $userInfoService, private ProviderService $providerService, + private ProvisioningService $provisioningService, ) { } public function isValidBearerToken(Provider $provider, string $bearerToken): ?string { $userInfo = $this->userInfoService->userinfo($provider, $bearerToken); - $uidAttribute = $this->providerService->getSetting($provider->getId(), ProviderService::SETTING_MAPPING_UID, ProviderService::SETTING_MAPPING_UID_DEFAULT); - if (!isset($userInfo[$uidAttribute])) { - return null; - } - - return $userInfo[$uidAttribute]; + $providerId = $provider->getId(); + $uidAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_UID, ProviderService::SETTING_MAPPING_UID_DEFAULT); + // find the user ID + $uid = $this->provisioningService->getClaimValue($userInfo, $uidAttribute, $providerId); + return $uid ?: null; } public function getProvisioningStrategy(): string { diff --git a/src/components/SettingsForm.vue b/src/components/SettingsForm.vue index 5c84a250..874af2a0 100644 --- a/src/components/SettingsForm.vue +++ b/src/components/SettingsForm.vue @@ -67,6 +67,9 @@ placeholder="claim1 claim2 claim3">

{{ t('user_oidc', 'Attribute mapping') }}

+ + {{ t('user_oidc', 'Enable nested and fallback claim mappings (like "{example}")', { example: 'custom.nickname | profile.name | name' }) }} +

true, 'groupWhitelistRegex' => '1', 'restrictLoginToGroups' => true, + 'nestedAndFallbackClaims' => true, ], ], [ @@ -133,6 +134,7 @@ public function testGetProvidersWithSettings() { 'groupProvisioning' => true, 'groupWhitelistRegex' => '1', 'restrictLoginToGroups' => true, + 'nestedAndFallbackClaims' => true, ], ], ], $this->providerService->getProvidersWithSettings()); @@ -171,6 +173,7 @@ public function testSetSettings() { 'mappingGender' => 'gender', 'groupWhitelistRegex' => '', 'restrictLoginToGroups' => false, + 'nestedAndFallbackClaims' => false, ]; $this->config->expects(self::any()) ->method('getAppValue') @@ -206,6 +209,7 @@ public function testSetSettings() { [Application::APP_ID, 'provider-1-' . ProviderService::SETTING_GROUP_PROVISIONING, '', '1'], [Application::APP_ID, 'provider-1-' . ProviderService::SETTING_GROUP_WHITELIST_REGEX, '', ''], [Application::APP_ID, 'provider-1-' . ProviderService::SETTING_RESTRICT_LOGIN_TO_GROUPS, '', '0'], + [Application::APP_ID, 'provider-1-' . ProviderService::SETTING_RESOLVE_NESTED_AND_FALLBACK_CLAIMS_MAPPING, '', '0'], ]); Assert::assertEquals( diff --git a/tests/unit/Service/ProvisioningServiceTest.php b/tests/unit/Service/ProvisioningServiceTest.php index bf3369c8..3d449e6d 100644 --- a/tests/unit/Service/ProvisioningServiceTest.php +++ b/tests/unit/Service/ProvisioningServiceTest.php @@ -143,6 +143,7 @@ public function testProvisionUserAutoProvisioning(): void { [$providerId, ProviderService::SETTING_MAPPING_BIOGRAPHY, 'biography', 'biography'], [$providerId, ProviderService::SETTING_MAPPING_PHONE, 'phone_number', 'phone_number'], [$providerId, ProviderService::SETTING_MAPPING_GENDER, 'gender', 'gender'], + [$providerId, ProviderService::SETTING_RESOLVE_NESTED_AND_FALLBACK_CLAIMS_MAPPING, '0', '0'], ] )); @@ -214,6 +215,7 @@ public function testProvisionUserInvalidProperties(): void { [$providerId, ProviderService::SETTING_MAPPING_BIOGRAPHY, 'biography', 'biography'], [$providerId, ProviderService::SETTING_MAPPING_PHONE, 'phone_number', 'phone_number'], [$providerId, ProviderService::SETTING_MAPPING_GENDER, 'gender', 'gender'], + [$providerId, ProviderService::SETTING_RESOLVE_NESTED_AND_FALLBACK_CLAIMS_MAPPING, '0', '0'], ] )); @@ -257,7 +259,7 @@ public function testProvisionUserInvalidProperties(): void { ->method('getProperty') ->with('twitter') ->willReturn($property); - + $this->accountManager->expects(self::once()) ->method('getAccount')