diff --git a/apps/oauth2/appinfo/routes.php b/apps/oauth2/appinfo/routes.php index 4ba398e13431f..86d39b6a3beae 100644 --- a/apps/oauth2/appinfo/routes.php +++ b/apps/oauth2/appinfo/routes.php @@ -43,5 +43,10 @@ 'url' => '/api/v1/token', 'verb' => 'POST' ], + [ + 'name' => 'OauthApi#getUserInfo', + 'url' => '/api/v1/userinfo', + 'verb' => 'GET' + ], ], ]; diff --git a/apps/oauth2/lib/Controller/OauthApiController.php b/apps/oauth2/lib/Controller/OauthApiController.php index 46b68b1d5859a..e021e5a29acd0 100644 --- a/apps/oauth2/lib/Controller/OauthApiController.php +++ b/apps/oauth2/lib/Controller/OauthApiController.php @@ -41,6 +41,9 @@ use OCP\Authentication\Exceptions\InvalidTokenException; use OCP\DB\Exception; use OCP\IRequest; +use OCP\IUserSession; +use OCP\IURLGenerator; +use OCP\IConfig; use OCP\Security\Bruteforce\IThrottler; use OCP\Security\ICrypto; use OCP\Security\ISecureRandom; @@ -62,6 +65,9 @@ public function __construct( private LoggerInterface $logger, private IThrottler $throttler, private ITimeFactory $timeFactory, + private IUserSession $userSession, + private IURLGenerator $urlGenerator, + private IConfig $config, ) { parent::__construct($appName, $request); } @@ -226,4 +232,45 @@ public function getToken( ] ); } + + /** + * @PublicPage + * @NoCSRFRequired + * + * @return JSONResponse + */ + public function getUserInfo(): JSONResponse { + $user = $this->userSession->getUser(); + if ($user === null) { + return new JSONResponse([ + 'error' => 'user_not_found', + ], Http::STATUS_NOT_FOUND); + } + $displayName = $user->getDisplayName(); + $userId = $user->getUID(); + + $userInfo = [ + 'sub' => $userId, + 'name' => $displayName, + 'email' => $user->getEMailAddress(), + 'picture' => $this->urlGenerator->getAbsoluteURL( + $this->urlGenerator->linkToRoute('core.avatar.getAvatar', [ + 'userId' => $userId, + 'size' => 512 + ])) + ]; + + $oauthConf = $this->config->getSystemValue('oauth2', ['process_name' => false]); + if ($oauthConf["process_name"] === true && + key_exists("separator", $oauthConf) && + key_exists("first_name_position", $oauthConf) && + key_exists("family_name_position", $oauthConf) && + $oauthConf["separator"] !== "" + ) { + $partedName = explode($oauthConf["separator"], $displayName); + $userInfo['given_name'] = $partedName[$oauthConf["first_name_position"]]; + $userInfo['family_name'] = $partedName[$oauthConf["family_name_position"]] ?? $partedName[0]; + } + return new JSONResponse($userInfo); + } } diff --git a/apps/oauth2/tests/Controller/OauthApiControllerTest.php b/apps/oauth2/tests/Controller/OauthApiControllerTest.php index eec38890e05b5..d38d87969ef09 100644 --- a/apps/oauth2/tests/Controller/OauthApiControllerTest.php +++ b/apps/oauth2/tests/Controller/OauthApiControllerTest.php @@ -39,7 +39,10 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IConfig; use OCP\IRequest; +use OCP\IURLGenerator; +use OCP\IUserSession; use OCP\Security\Bruteforce\IThrottler; use OCP\Security\ICrypto; use OCP\Security\ISecureRandom; @@ -88,6 +91,10 @@ protected function setUp(): void { $this->throttler = $this->createMock(IThrottler::class); $this->logger = $this->createMock(LoggerInterface::class); $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->user = $this->createMock(IUser::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->config = $this->createMock(IConfig::class); $this->oauthApiController = new OauthApiController( 'oauth2', @@ -616,4 +623,47 @@ public function testRefreshTokenExpiredAppToken() { $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret')); } + + public function testGetUserInfo(): void { + $this->user->method('getDisplayName')->willReturn('Test User'); + $this->user->method('getUID')->willReturn('testuser'); + $this->user->method('getEMailAddress')->willReturn('testuser@example.com'); + + $this->userSession->method('getUser')->willReturn($this->user); + $this->urlGenerator->method('getAbsoluteURL')->willReturn('http://localhost/avatar.png'); + $this->config->method('getSystemValue')->willReturn(['process_name' => false]); + + $response = $this->oauthApiController->getUserInfo(); + + $this->assertInstanceOf(JSONResponse::class, $response); + $this->assertEquals('Test User', $response->getData()['name']); + $this->assertEquals('testuser', $response->getData()['sub']); + $this->assertEquals('testuser@example.com', $response->getData()['email']); + $this->assertEquals('http://localhost/avatar.png', $response->getData()['picture']); + } + + public function testGetUserInfoWithSeparate(): void { + $this->user->method('getDisplayName')->willReturn('Test User'); + $this->user->method('getUID')->willReturn('testuser'); + $this->user->method('getEMailAddress')->willReturn('testuser@example.com'); + + $this->userSession->method('getUser')->willReturn($this->user); + $this->urlGenerator->method('getAbsoluteURL')->willReturn('http://localhost/avatar.png'); + + $this->config->method('getSystemValue') + ->willReturn([ + 'process_name' => true, + 'separator' => ' ', + 'first_name_position' => 0, + 'family_name_position' => 1 + ]); + $response = $this->oauthApiController->getUserInfo(); + + $this->assertInstanceOf(JSONResponse::class, $response); + $this->assertEquals('testuser', $response->getData()['sub']); + $this->assertEquals('testuser@example.com', $response->getData()['email']); + $this->assertEquals('http://localhost/avatar.png', $response->getData()['picture']); + $this->assertEquals('Test', $response->getData()['given_name']); + $this->assertEquals('User', $response->getData()['family_name']); + } } diff --git a/config/config.sample.php b/config/config.sample.php index 364e573974a12..7b76b1e59a473 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1962,7 +1962,7 @@ /** * Blacklist characters from being used in filenames. This is useful if you * have a filesystem or OS which does not support certain characters like windows. - * + * * The '/' and '\' characters are always forbidden. * * Example for windows systems: ``array('?', '<', '>', ':', '*', '|', '"', chr(0), "\n", "\r")`` @@ -2420,4 +2420,21 @@ * Defaults to ``true`` */ 'enable_non-accessible_features' => true, + +/** + * OAuth2 userinfo endpoint settings + * + * The 'process_name' key defines the need to separate the name into given_name and family_name. If false then given_name and family_name are not passed. + * Defaults to ``false`` + * The 'separator' key is a symbol separating the first and last name. + * The key 'first_name_position' is the position of given_name in the name. + * The key 'family_name_position' is the position of family_name in the name. + */ +'oauth2' => + [ + 'process_name' => false, + 'separator' => ' ', + 'first_name_position' => 0, + 'family_name_position' => 1, + ], ];