From a13714a344c61da9eae7c8832e8f89588700cd84 Mon Sep 17 00:00:00 2001 From: H2CK Date: Fri, 29 May 2026 10:19:14 +0200 Subject: [PATCH] Extended claims in JWT access token Signed-off-by: H2CK --- lib/Util/JwtGenerator.php | 114 ++++++++++++- package-lock.json | 72 +++----- tests/Unit/Util/JwtGeneratorTest.php | 243 ++++++++++++++++++++++----- 3 files changed, 327 insertions(+), 102 deletions(-) diff --git a/lib/Util/JwtGenerator.php b/lib/Util/JwtGenerator.php index 5897f8eb..5c5377d5 100644 --- a/lib/Util/JwtGenerator.php +++ b/lib/Util/JwtGenerator.php @@ -326,7 +326,6 @@ public function generateIdToken(AccessToken $accessToken, Client $client, string * @param Client $client * @param string $issuerProtocol * @param string $issuerHost - * @param bool $atHash * @return string * @throws PropertyDoesNotExistException * @throws JwtCreationErrorException @@ -342,6 +341,7 @@ public function generateAccessToken(AccessToken $accessToken, Client $client, st $uid = $accessToken->getUserId(); $user = $this->userManager->get($uid); $groups = $this->groupManager->getUserGroups($user); + $account = $this->accountManager->getAccount($user); $aud = $accessToken->getResource(); if (!isset($aud) || trim($aud)==='') { $aud = $client->getClientIdentifier(); @@ -356,31 +356,129 @@ public function generateAccessToken(AccessToken $accessToken, Client $client, st 'iat' => $this->time->getTime(), 'acr' => '0', 'client_id' => $client->getClientIdentifier(), + 'azp' => $client->getClientIdentifier(), + 'preferred_username' => $uid, 'scope' => $accessToken->getScope(), 'jti' => strval($accessToken->getId()), ]; $roles = []; - // Fetch roles + $rolesDisplayName = []; foreach ($groups as $group) { array_push($roles, $group->getGID()); + $displayName = $group->getDisplayName(); + if ($displayName !== null && $displayName !== '') { + array_push($rolesDisplayName, $displayName); + } else { + array_push($rolesDisplayName, $group->getGID()); + } + } + + $groupClaimType = $this->appConfig->getAppValueString(Application::APP_CONFIG_GROUP_CLAIM_TYPE, Application::GROUP_CLAIM_TYPE_GID); + $rolesClaimType = $this->appConfig->getAppValueString(Application::APP_CONFIG_ROLES_CLAIM_TYPE, 'null'); + if ($rolesClaimType !== null && $rolesClaimType === 'null') { + $rolesClaimType = $groupClaimType; } // Check for scopes roles, groups and entitlements (not supported) $scopeArray = preg_split('/ +/', $accessToken->getScope()); if (in_array("roles", $scopeArray)) { - $roles_payload = [ - 'roles' => $roles - ]; + if ($rolesClaimType === Application::GROUP_CLAIM_TYPE_DISPLAYNAME) { + $roles_payload = [ + 'roles' => $rolesDisplayName + ]; + } else { + $roles_payload = [ + 'roles' => $roles + ]; + } $jwt_payload = array_merge($jwt_payload, $roles_payload); } if (in_array("groups", $scopeArray)) { - $roles_payload = [ - 'groups' => $roles - ]; + if ($groupClaimType === Application::GROUP_CLAIM_TYPE_DISPLAYNAME) { + $roles_payload = [ + 'groups' => $rolesDisplayName + ]; + } else { + $roles_payload = [ + 'groups' => $roles + ]; + } $jwt_payload = array_merge($jwt_payload, $roles_payload); } + $restrictUserInformationArr = explode(' ', strtolower(trim($this->appConfig->getAppValueString(Application::APP_CONFIG_RESTRICT_USER_INFORMATION, Application::DEFAULT_RESTRICT_USER_INFORMATION)))); + $restrictUserInformationPersonalArr = [ Application::DEFAULT_ALLOW_USER_SETTINGS ]; + if ($this->appConfig->getAppValueString(Application::APP_CONFIG_ALLOW_USER_SETTINGS, Application::DEFAULT_ALLOW_USER_SETTINGS) != Application::DEFAULT_ALLOW_USER_SETTINGS) { + $restrictUserInformationPersonalArr = explode(' ', strtolower(trim($this->config->getUserValue($uid, Application::APP_ID, Application::APP_CONFIG_RESTRICT_USER_INFORMATION, Application::DEFAULT_RESTRICT_USER_INFORMATION)))); + } + + if (in_array("profile", $scopeArray)) { + $profile = [ + 'updated_at' => $user->getLastLogin(), + ]; + if ($account->getProperty(\OCP\Accounts\IAccountManager::PROPERTY_DISPLAYNAME)->getValue() != '') { + $displayName = $account->getProperty(\OCP\Accounts\IAccountManager::PROPERTY_DISPLAYNAME)->getValue(); + $names = $this->converter->splitFullName($displayName); + $profile = array_merge($profile, [ + 'name' => $displayName, + 'family_name' => $names[0], + 'given_name' => $names[1], + 'middle_name' => $names[2] + ]); + } else { + $profile = array_merge($profile, ['name' => $user->getDisplayName()]); + } + if ($account->getProperty(\OCP\Accounts\IAccountManager::PROPERTY_WEBSITE)->getValue() != '' && !in_array('website', $restrictUserInformationArr) && !in_array('website', $restrictUserInformationPersonalArr)) { + $profile = array_merge($profile, + ['website' => $account->getProperty(\OCP\Accounts\IAccountManager::PROPERTY_WEBSITE)->getValue()]); + } + if ($account->getProperty(\OCP\Accounts\IAccountManager::PROPERTY_PHONE)->getValue() != '' && !in_array('phone', $restrictUserInformationArr) && !in_array('phone', $restrictUserInformationPersonalArr)) { + $profile = array_merge($profile, + ['phone_number' => $account->getProperty(\OCP\Accounts\IAccountManager::PROPERTY_PHONE)->getValue()]); + } + if ($account->getProperty(\OCP\Accounts\IAccountManager::PROPERTY_ADDRESS)->getValue() != '' && !in_array('address', $restrictUserInformationArr) && !in_array('address', $restrictUserInformationPersonalArr)) { + $profile = array_merge($profile, + ['address' => + [ 'formatted' => $account->getProperty(\OCP\Accounts\IAccountManager::PROPERTY_ADDRESS)->getValue()]]); + } + if (!in_array('avatar', $restrictUserInformationArr) && !in_array('avatar', $restrictUserInformationPersonalArr)) { + $profile = array_merge($profile, + ['picture' => $issuer . '/avatar/' . rawurlencode($uid) . '/64']); + } + $jwt_payload = array_merge($jwt_payload, $profile); + } + + if (in_array("email", $scopeArray) && $user->getEMailAddress() !== null) { + $emailProperty = $account->getProperty(\OCP\Accounts\IAccountManager::PROPERTY_EMAIL); + $clientEmailRegex = $client->getEmailRegex(); + if ($clientEmailRegex !== '') { + $this->logger->debug('Found regex for email: ' . $clientEmailRegex); + $emailCollection = $account->getPropertyCollection(\OCP\Accounts\IAccountManager::COLLECTION_EMAIL); + foreach ($emailCollection->getProperties() as $emailPropertyEntry) { + $this->logger->debug('Performing check for mail ' . $emailPropertyEntry->getValue()); + if (preg_match('/'.$clientEmailRegex.'/', $emailPropertyEntry->getValue())) { + $this->logger->debug('Regex matches'); + $emailProperty = $emailPropertyEntry; + } + } + } + + $email = [ + 'email' => $emailProperty->getValue(), + ]; + if ($this->appConfig->getAppValueString(Application::APP_CONFIG_OVERWRITE_EMAIL_VERIFIED) == 'true') { + $email = array_merge($email, ['email_verified' => true]); + } else { + if ($emailProperty->getVerified() === \OCP\Accounts\IAccountManager::VERIFIED) { + $email = array_merge($email, ['email_verified' => true]); + } else { + $email = array_merge($email, ['email_verified' => false]); + } + } + $jwt_payload = array_merge($jwt_payload, $email); + } + $payload = json_encode($jwt_payload); $base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload)); diff --git a/package-lock.json b/package-lock.json index 6bc51963..18d1d087 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5681,9 +5681,9 @@ "peer": true }, "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "dev": true, "license": "MIT", "peer": true, @@ -5696,7 +5696,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.14.0", + "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -5725,23 +5725,6 @@ "license": "MIT", "peer": true }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bonjour-service": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", @@ -5854,14 +5837,14 @@ } }, "node_modules/browserify-sign": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.5.tgz", - "integrity": "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.6.tgz", + "integrity": "sha512-sd+Q65fjlWCYWtZKXiKfrUc8d+4jtp/8f0W2NkwzLtoW4bI6UDnWusLWIurHnmurW0XShIRxpwiOX4EoPtXUAg==", "dev": true, "license": "ISC", "peer": true, "dependencies": { - "bn.js": "^5.2.2", + "bn.js": "^5.2.3", "browserify-rsa": "^4.1.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", @@ -8378,16 +8361,16 @@ } }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "~1.20.3", + "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", @@ -8406,7 +8389,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "~6.14.0", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", @@ -8444,23 +8427,6 @@ "license": "MIT", "peer": true }, - "node_modules/express/node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/exsolve": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", @@ -13216,9 +13182,9 @@ } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "dev": true, "license": "BSD-3-Clause", "peer": true, @@ -17387,9 +17353,9 @@ } }, "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, "license": "MIT", "peer": true, diff --git a/tests/Unit/Util/JwtGeneratorTest.php b/tests/Unit/Util/JwtGeneratorTest.php index 53ad22e0..379f5d4f 100644 --- a/tests/Unit/Util/JwtGeneratorTest.php +++ b/tests/Unit/Util/JwtGeneratorTest.php @@ -132,7 +132,7 @@ public function setUp(): void { $this->accountManager, $this->logger ); - $this->credentialsManager = $this->getMockBuilder(ICredentialsManager::class)->getMock(); + $this->credentialsManager = $this->getMockBuilder(ICredentialsManager::class)->getMock(); $this->credentialService = new CredentialService( $this->credentialsManager, $this->appConfig, @@ -159,10 +159,136 @@ public function setUp(): void { ); } - // public function testGenerateIdToken() { - // TODO create test - // $result = $this->generator->generateIdToken(); - // } + public function testGenerateIdToken() { + // Prepare key material for test + $config = array( + "digest_alg" => 'sha512', + "private_key_bits" => 4096, + "private_key_type" => OPENSSL_KEYTYPE_RSA + ); + $keyPair = openssl_pkey_new($config); + $privateKey = null; + openssl_pkey_export($keyPair, $privateKey); + $keyDetails = openssl_pkey_get_details($keyPair); + $publicKey = $keyDetails['key']; + $modulus = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($keyDetails['rsa']['n'])); + $exponent = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($keyDetails['rsa']['e'])); + $kid = $this->guidv4(); + + // Mock necessary methods + $this->appConfig + ->method('getAppValueString') + ->willReturnCallback(function($key, $default = '') use ($privateKey, $publicKey, $modulus, $exponent, $kid) { + $map = [ + 'dynamic_client_registration' => 'true', + 'expire_time' => '3600', + 'integrate_avatar' => 'id_token', + 'overwrite_email_verified' => 'true', + 'private_key' => $privateKey, + 'public_key' => $publicKey, + 'public_key_n' => $modulus, + 'public_key_e' => $exponent, + 'kid' => $kid, + ]; + return $map[$key] ?? $default; + }); + $this->credentialsManager + ->method('retrieve') + ->willReturn($privateKey); + + // Create a mock user + $testEmail = 'testuser@example.com'; + $mockUser = $this->createMock(IUser::class); + $mockUser->method('getEMailAddress')->willReturn($testEmail); + $mockUser->method('getQuota')->willReturn('1000000'); + $this->userManager + ->method('get') + ->willReturn($mockUser); + + $this->groupManager + ->method('getUserGroups') + ->willReturn([]); + + // Create mock account and account properties + $mockAccount = $this->createMock(IAccount::class); + $mockAccountProperty = $this->createMock(IAccountProperty::class); + $mockAccountProperty->method('getValue')->willReturn(''); + + // Special handling for email property + $mockEmailProperty = $this->createMock(IAccountProperty::class); + $mockEmailProperty->method('getValue')->willReturn($testEmail); + + $mockAccount + ->method('getProperty') + ->willReturnCallback(function($prop) use ($mockEmailProperty, $mockAccountProperty) { + if ($prop === \OCP\Accounts\IAccountManager::PROPERTY_EMAIL) { + return $mockEmailProperty; + } + return $mockAccountProperty; + }); + $this->accountManager + ->method('getAccount') + ->willReturn($mockAccount); + + $user_id = '34'; + $protocol = 'https'; + $issuer = 'issuer.url'; + $scope = 'openid profile email roles'; + + $client = new Client('TEST', 'http://redirect.uri/callback', 'RS256', 'confidential', 'code', 'jwt', false); + $client->setClientIdentifier('TESTCLIENTIDENTIFIER'); + $client->setId(1); + + $code = $this->secureRandom->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS); + $accessToken = new AccessToken(); + $accessToken->setClientId($client->getId()); + $accessToken->setUserId($user_id); + $accessToken->setHashedCode(hash('sha512', $code)); + $accessToken->setScope(substr($scope, 0, 128)); + $accessToken->setCreated($this->time->getTime()); + $accessToken->setRefreshed($this->time->getTime()); + $accessToken->setNonce('12345678'); + + // Execute test + $result = $this->generator->generateIdToken( + $accessToken, + $client, + $protocol, + $issuer, + false + ); + + // Decode received JWT + $oidcKey = [ + 'kty' => 'RSA', + 'use' => 'sig', + 'key_ops' => [ 'verify' ], + 'alg' => 'RS256', + 'kid' => $this->appConfig->getAppValueString('kid'), + 'n' => $this->appConfig->getAppValueString('public_key_n'), + 'e' => $this->appConfig->getAppValueString('public_key_e'), + ]; + + $jwks = [ + 'keys' => [ + $oidcKey, + ], + ]; + + $decodedStdClass = JWT::decode($result, JWK::parseKeySet($jwks)); + $decodedJwt = (array) $decodedStdClass; + + // Test if decoded JWT contains necessary values + $this->assertEquals($protocol . "://" . $issuer, $decodedJwt['iss']); + $this->assertEquals($user_id, $decodedJwt['sub']); + $this->assertEquals($client->getClientIdentifier(), $decodedJwt['aud']); + $this->assertEquals($scope, $decodedJwt['scope']); + $this->assertEquals($client->getClientIdentifier(), $decodedJwt['azp']); + $this->assertArrayHasKey('email', $decodedJwt); + $this->assertEquals('testuser@example.com', $decodedJwt['email']); + $this->assertArrayHasKey('nonce', $decodedJwt); + $this->assertEquals('12345678', $decodedJwt['nonce']); + } public function testGenerateOpaqueAccessToken() { $client = new Client('TEST', 'http://redirect.uri/callback', 'RS256', 'confidential', 'code', 'opaque', false); @@ -201,38 +327,61 @@ public function testGenerateJwtAccessToken() { $publicKey = $keyDetails['key']; $modulus = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($keyDetails['rsa']['n'])); $exponent = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($keyDetails['rsa']['e'])); + $kid = $this->guidv4(); // Mock necessary methods $this->appConfig ->method('getAppValueString') - ->willReturnMap([ - ['dynamic_client_registration', 'false', 'true'], - ['expire_time', Application::DEFAULT_EXPIRE_TIME, '3600'], - ['integrate_avatar', 'id_token'], - ['overwrite_email_verified', 'true'], - ['private_key', $privateKey], - ['public_key', $publicKey], - ['public_key_n', $modulus], - ['public_key_e', $exponent], - ['kid', $this->guidv4()], - ]); + ->willReturnCallback(function($key, $default = '') use ($privateKey, $publicKey, $modulus, $exponent, $kid) { + $map = [ + 'dynamic_client_registration' => 'true', + 'expire_time' => '3600', + 'integrate_avatar' => 'id_token', + 'overwrite_email_verified' => 'true', + 'private_key' => $privateKey, + 'public_key' => $publicKey, + 'public_key_n' => $modulus, + 'public_key_e' => $exponent, + 'kid' => $kid, + ]; + return $map[$key] ?? $default; + }); $this->credentialsManager ->method('retrieve') ->willReturn($privateKey); + + // Create a mock user + $testEmail = 'testuser@example.com'; + $mockUser = $this->createMock(IUser::class); + $mockUser->method('getEMailAddress')->willReturn($testEmail); $this->userManager ->method('get') - ->willReturnCallBack ( - function ($arg) { - return null; - } - ); + ->willReturn($mockUser); + $this->groupManager ->method('getUserGroups') - ->willReturnCallBack ( - function ($arg) { - return []; + ->willReturn([]); + + // Create mock account and account properties + $mockAccount = $this->createMock(IAccount::class); + $mockAccountProperty = $this->createMock(IAccountProperty::class); + $mockAccountProperty->method('getValue')->willReturn(''); + + // Special handling for email property + $mockEmailProperty = $this->createMock(IAccountProperty::class); + $mockEmailProperty->method('getValue')->willReturn($testEmail); + + $mockAccount + ->method('getProperty') + ->willReturnCallback(function($prop) use ($mockEmailProperty, $mockAccountProperty) { + if ($prop === \OCP\Accounts\IAccountManager::PROPERTY_EMAIL) { + return $mockEmailProperty; } - ); + return $mockAccountProperty; + }); + $this->accountManager + ->method('getAccount') + ->willReturn($mockAccount); $user_id = '34'; $protocol = 'https'; @@ -290,6 +439,8 @@ function ($arg) { $this->assertEquals($resource, $decodedJwt['aud']); $this->assertEquals($scope, $decodedJwt['scope']); $this->assertEquals($client->getClientIdentifier(), $decodedJwt['client_id']); + $this->assertArrayHasKey('email', $decodedJwt); + $this->assertEquals('testuser@example.com', $decodedJwt['email']); } public function testGenerateJwtAccessTokenException() { @@ -308,31 +459,41 @@ public function testGenerateJwtAccessTokenException() { $publicKey = $keyDetails['key']; $modulus = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($keyDetails['rsa']['n'])); $exponent = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($keyDetails['rsa']['e'])); + $kid = $this->guidv4(); // Mock necessary methods $this->appConfig ->method('getAppValueString') - ->willReturnMap([ - ['dynamic_client_registration', 'false', 'true'], - ['expire_time', Application::DEFAULT_EXPIRE_TIME, '3600'], - ['integrate_avatar', 'id_token'], - ['overwrite_email_verified', 'true'], - ['private_key', $privateKey], - ['public_key', $publicKey], - ['public_key_n', $modulus], - ['public_key_e', $exponent], - ['kid', $this->guidv4()], - ]); + ->willReturnCallback(function($key, $default = '') use ($privateKey, $publicKey, $modulus, $exponent, $kid) { + $map = [ + 'dynamic_client_registration' => 'true', + 'expire_time' => '3600', + 'integrate_avatar' => 'id_token', + 'overwrite_email_verified' => 'true', + 'private_key' => $privateKey, + 'public_key' => $publicKey, + 'public_key_n' => $modulus, + 'public_key_e' => $exponent, + 'kid' => $kid, + ]; + return $map[$key] ?? $default; + }); $this->credentialsManager ->method('retrieve') ->willReturn($privateKey); + $mockUser = $this->createMock(IUser::class); $this->userManager ->method('get') - ->willReturnCallBack ( - function ($arg) { - return null; - } - ); + ->willReturn($mockUser); + $mockAccount = $this->createMock(IAccount::class); + $mockAccountProperty = $this->createMock(IAccountProperty::class); + $mockAccountProperty->method('getValue')->willReturn(''); + $mockAccount + ->method('getProperty') + ->willReturn($mockAccountProperty); + $this->accountManager + ->method('getAccount') + ->willReturn($mockAccount); $this->groupManager ->method('getUserGroups') ->willReturnCallBack (