From 7e326ae89ecbb53500ca76f3d74cdb2711b8b539 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kruli=C5=A1?= Date: Fri, 23 Jan 2026 16:14:33 +0100 Subject: [PATCH 1/3] Some code polishing. --- .../presenters/RegistrationPresenter.php | 14 -------------- app/V1Module/presenters/UsersPresenter.php | 18 +++++++++--------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/app/V1Module/presenters/RegistrationPresenter.php b/app/V1Module/presenters/RegistrationPresenter.php index 87f71151f..b5b5e9cfa 100644 --- a/app/V1Module/presenters/RegistrationPresenter.php +++ b/app/V1Module/presenters/RegistrationPresenter.php @@ -18,11 +18,9 @@ use App\Model\Entity\Instance; use App\Model\Repository\Groups; use App\Model\Repository\Logins; -use App\Model\Repository\ExternalLogins; use App\Model\Repository\Instances; use App\Model\View\UserViewFactory; use App\Security\AccessManager; -use App\Helpers\ExternalLogin\ExternalServiceAuthenticator; use App\Helpers\EmailVerificationHelper; use App\Helpers\RegistrationConfig; use App\Helpers\InvitationHelper; @@ -47,12 +45,6 @@ class RegistrationPresenter extends BasePresenter */ public $logins; - /** - * @var ExternalLogins - * @inject - */ - public $externalLogins; - /** * @var AccessManager * @inject @@ -71,12 +63,6 @@ class RegistrationPresenter extends BasePresenter */ public $groups; - /** - * @var ExternalServiceAuthenticator - * @inject - */ - public $externalServiceAuthenticator; - /** * @var EmailVerificationHelper * @inject diff --git a/app/V1Module/presenters/UsersPresenter.php b/app/V1Module/presenters/UsersPresenter.php index 37a6594b5..6966236eb 100644 --- a/app/V1Module/presenters/UsersPresenter.php +++ b/app/V1Module/presenters/UsersPresenter.php @@ -351,20 +351,20 @@ private function changeUserEmail(User $user, ?string $email) * Change first name and last name and check if user can change them. * @param User $user * @param null|string $titlesBefore - * @param null|string $firstname - * @param null|string $lastname + * @param null|string $firstName + * @param null|string $lastName * @param null|string $titlesAfter * @throws ForbiddenRequestException */ private function changePersonalData( User $user, ?string $titlesBefore, - ?string $firstname, - ?string $lastname, + ?string $firstName, + ?string $lastName, ?string $titlesAfter ) { if ( - ($titlesBefore !== null || $firstname !== null || $lastname !== null || $titlesAfter !== null) && + ($titlesBefore !== null || $firstName !== null || $lastName !== null || $titlesAfter !== null) && !$this->userAcl->canUpdatePersonalData($user) ) { throw new ForbiddenRequestException("You cannot update personal data"); @@ -374,12 +374,12 @@ private function changePersonalData( $user->setTitlesBeforeName(trim($titlesBefore)); } - if ($firstname && trim($firstname)) { - $user->setFirstName(trim($firstname)); + if ($firstName && trim($firstName)) { + $user->setFirstName(trim($firstName)); } - if ($lastname && trim($lastname)) { - $user->setLastName(trim($lastname)); + if ($lastName && trim($lastName)) { + $user->setLastName(trim($lastName)); } if ($titlesAfter !== null) { From 70a64a31f490be1b638d5a384a02524bac0c5e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kruli=C5=A1?= Date: Fri, 23 Jan 2026 16:27:53 +0100 Subject: [PATCH 2/3] Adding an endpoint that looks up a user by an external id. --- app/V1Module/presenters/UsersPresenter.php | 23 ++++++++++++++++++++++ app/V1Module/router/RouterFactory.php | 1 + 2 files changed, 24 insertions(+) diff --git a/app/V1Module/presenters/UsersPresenter.php b/app/V1Module/presenters/UsersPresenter.php index 6966236eb..f6f458940 100644 --- a/app/V1Module/presenters/UsersPresenter.php +++ b/app/V1Module/presenters/UsersPresenter.php @@ -214,6 +214,29 @@ public function actionDetail(string $id) $this->sendSuccessResponse($this->userViewFactory->getUser($user)); } + public function checkFindByExternalLogin(string $service, string $externalId) + { + $user = $this->externalLogins->getUser($service, $externalId); + if (!$user) { + throw new NotFoundException("User with given external login not found."); + } + if (!$this->userAcl->canViewPublicData($user)) { + throw new ForbiddenRequestException(); + } + } + + /** + * Get details of a user identified via external login. + * @GET + */ + #[Path("service", new VString(1), "External authentication service name", required: true)] + #[Path("externalId", new VString(1), "External user identifier", required: true)] + public function actionFindByExternalLogin(string $service, string $externalId) + { + $user = $this->externalLogins->getUser($service, $externalId); + $this->sendSuccessResponse($user ? $this->userViewFactory->getUser($user) : null); + } + public function checkDelete(string $id) { $user = $this->users->findOrThrow($id); diff --git a/app/V1Module/router/RouterFactory.php b/app/V1Module/router/RouterFactory.php index aeeb3d3f0..3a3bd57bf 100644 --- a/app/V1Module/router/RouterFactory.php +++ b/app/V1Module/router/RouterFactory.php @@ -506,6 +506,7 @@ private static function createUsersRoutes(string $prefix): RouteList $router[] = new PostRoute("$prefix/accept-invitation", "Registration:acceptInvitation"); $router[] = new GetRoute("$prefix/", "Users:detail"); + $router[] = new GetRoute("$prefix/external-login//", "Users:findByExternalLogin"); $router[] = new PostRoute("$prefix//invalidate-tokens", "Users:invalidateTokens"); $router[] = new DeleteRoute("$prefix/", "Users:delete"); $router[] = new GetRoute("$prefix//groups", "Users:groups"); From c5493224f8d8c91b4af1571be98b1871cd90fd41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kruli=C5=A1?= Date: Fri, 23 Jan 2026 17:07:51 +0100 Subject: [PATCH 3/3] Updating tests and swagger OAPI doc. --- docs/swagger.yaml | 27 ++++++++++++++++++++ fixtures/demo/20-groups.neon | 7 ++++++ tests/Presenters/UsersPresenter.phpt | 37 +++++++++++++++++++++++++--- 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 50a631cd1..716b39258 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -5548,6 +5548,33 @@ paths: responses: '200': description: 'Placeholder response' + '/v1/users/external-login/{service}/{externalId}': + get: + summary: 'Get details of a user identified via external login.' + description: 'Get details of a user identified via external login.' + operationId: usersPresenterActionFindByExternalLogin + parameters: + - + name: service + in: path + description: 'External authentication service name' + required: true + schema: + type: string + minLength: 1 + nullable: false + - + name: externalId + in: path + description: 'External user identifier' + required: true + schema: + type: string + minLength: 1 + nullable: false + responses: + '200': + description: 'Placeholder response' '/v1/users/{id}/invalidate-tokens': post: summary: 'Invalidate all existing tokens issued for given user' diff --git a/fixtures/demo/20-groups.neon b/fixtures/demo/20-groups.neon index 27e31fd4b..1c047b4d9 100644 --- a/fixtures/demo/20-groups.neon +++ b/fixtures/demo/20-groups.neon @@ -155,6 +155,13 @@ App\Model\Entity\Login: - 'demoGroupMember\@example.com' - "password" +App\Model\Entity\ExternalLogin: + demoGroupMemberExternalLogin: + __construct: + - @demoGroupMember1 + - 'ext-service' + - 'external-login1' + App\Model\Entity\GroupInvitation: validInviation: __construct: diff --git a/tests/Presenters/UsersPresenter.phpt b/tests/Presenters/UsersPresenter.phpt index c0b32a956..a99c985fb 100644 --- a/tests/Presenters/UsersPresenter.phpt +++ b/tests/Presenters/UsersPresenter.phpt @@ -5,7 +5,6 @@ $container = require_once __DIR__ . "/../bootstrap.php"; use App\Exceptions\ForbiddenRequestException; use App\Helpers\EmailVerificationHelper; use App\Helpers\AnonymizationHelper; -use App\Model\Entity\Exercise; use App\Model\Entity\Login; use App\Model\Entity\ExternalLogin; use App\Model\Entity\User; @@ -225,6 +224,36 @@ class TestUsersPresenter extends Tester\TestCase Assert::same($user->getId(), $result["payload"]["id"]); } + public function testFindByExternalLogin() + { + PresenterTestHelper::loginDefaultAdmin($this->container); + $extLogins = $this->presenter->externalLogins->findAll(); + Assert::count(1, $extLogins); + $el = $extLogins[0]; + + $payload = PresenterTestHelper::performPresenterRequest( + $this->presenter, + $this->presenterPath, + 'GET', + ['action' => 'findByExternalLogin', 'service' => $el->getAuthService(), 'externalId' => $el->getExternalId()] + ); + + Assert::same($el->getUser()->getId(), $payload["id"]); + } + + public function testFindByExternalLoginEmptyResult() + { + PresenterTestHelper::loginDefaultAdmin($this->container); + Assert::exception(function () { + PresenterTestHelper::performPresenterRequest( + $this->presenter, + $this->presenterPath, + 'GET', + ['action' => 'findByExternalLogin', 'service' => 'ext-service', 'externalId' => 'blabla'] + ); + }, App\Exceptions\NotFoundException::class); + } + public function testUpdateProfileWithoutEmailAndPassword() { PresenterTestHelper::loginDefaultAdmin($this->container); @@ -857,7 +886,7 @@ class TestUsersPresenter extends Tester\TestCase Assert::true($payload['privateData']['isExternal']); Assert::equal(['test-cas' => 'abc'], $payload['privateData']['externalIds']); - $els = $this->presenter->externalLogins->findAll(); + $els = $this->presenter->externalLogins->findBy(['authService' => 'test-cas']); Assert::count(1, $els); Assert::equal($user->getId(), $els[0]->getUser()->getId()); Assert::equal('abc', $els[0]->getExternalId()); @@ -890,7 +919,7 @@ class TestUsersPresenter extends Tester\TestCase Assert::true($payload['privateData']['isExternal']); Assert::equal(['test-cas' => 'abc'], $payload['privateData']['externalIds']); - $els = $this->presenter->externalLogins->findAll(); + $els = $this->presenter->externalLogins->findBy(['authService' => 'test-cas']); Assert::count(1, $els); Assert::equal($user->getId(), $els[0]->getUser()->getId()); Assert::equal('abc', $els[0]->getExternalId()); @@ -922,7 +951,7 @@ class TestUsersPresenter extends Tester\TestCase Assert::false($payload['privateData']['isExternal']); Assert::equal([], $payload['privateData']['externalIds']); - $els = $this->presenter->externalLogins->findAll(); + $els = $this->presenter->externalLogins->findBy(['authService' => 'test-cas']); Assert::count(0, $els); $this->users->refresh($user);