Skip to content

Commit a3ddfa0

Browse files
committed
Adding endpoints for creating, updating, and removing external IDs.
1 parent f7516da commit a3ddfa0

File tree

7 files changed

+120
-4
lines changed

7 files changed

+120
-4
lines changed

app/V1Module/presenters/UsersPresenter.php

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
use App\Exceptions\InvalidArgumentException;
88
use App\Exceptions\NotFoundException;
99
use App\Exceptions\WrongCredentialsException;
10+
use App\Model\Entity\ExternalLogin;
1011
use App\Model\Entity\Group;
1112
use App\Model\Entity\Login;
1213
use App\Model\Entity\SecurityEvent;
1314
use App\Model\Entity\User;
1415
use App\Model\Entity\UserUiData;
16+
use App\Model\Repository\ExternalLogins;
1517
use App\Model\Repository\Logins;
1618
use App\Model\Repository\SecurityEvents;
1719
use App\Exceptions\BadRequestException;
@@ -36,6 +38,12 @@ class UsersPresenter extends BasePresenter
3638
*/
3739
public $logins;
3840

41+
/**
42+
* @var ExternalLogins
43+
* @inject
44+
*/
45+
public $externalLogins;
46+
3947
/**
4048
* @var SecurityEvents
4149
* @inject
@@ -294,7 +302,7 @@ private function changeUserEmail(User $user, ?string $email)
294302
}
295303

296304
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
297-
throw new InvalidArgumentException("Provided email is not in correct format");
305+
throw new InvalidArgumentException('email', "Provided email is not in correct format");
298306
}
299307

300308
$oldEmail = $user->getEmail();
@@ -375,7 +383,7 @@ private function changeUserPassword(
375383

376384
if (!$password || !$passwordConfirm) {
377385
// old password was provided but the new ones not, illegal state
378-
throw new InvalidArgumentException("New password was not provided");
386+
throw new InvalidArgumentException('password|passwordConfirm', "New password was not provided");
379387
}
380388

381389
// passwords need to be handled differently
@@ -764,4 +772,76 @@ public function actionSetAllowed(string $id)
764772
$this->users->flush();
765773
$this->sendSuccessResponse($this->userViewFactory->getUser($user));
766774
}
775+
776+
public function checkUpdateExternalLogin(string $id, string $service)
777+
{
778+
$user = $this->users->findOrThrow($id);
779+
if (!$this->userAcl->canSetExternalIds($user)) {
780+
throw new ForbiddenRequestException();
781+
}
782+
783+
// in the future, we might consider cross-checking the service ID
784+
}
785+
786+
/**
787+
* Add or update existing external ID of given authentication service.
788+
* @POST
789+
* @param string $id identifier of the user
790+
* @param string $service identifier of the authentication service (login type)
791+
* @Param(type="post", name="externalId", validation="string:1..128")
792+
* @throws InvalidArgumentException
793+
*/
794+
public function actionUpdateExternalLogin(string $id, string $service)
795+
{
796+
$user = $this->users->findOrThrow($id);
797+
798+
// make sure the external ID is not used for another user
799+
$externalId = $this->getRequest()->getPost("externalId");
800+
$anotherUser = $this->externalLogins->getUser($service, $externalId);
801+
if ($anotherUser) {
802+
if ($anotherUser->getId() !== $id) {
803+
// oopsie, this external ID is alreay used for a different user
804+
throw new InvalidArgumentException('externalId', "This ID is already used by another user.");
805+
}
806+
// otherwise the external ID is already set to this user, so there is nothing to change...
807+
} else {
808+
// create/update external login entry
809+
$login = $this->externalLogins->findByUser($user, $service);
810+
if ($login) {
811+
$login->setExternalId($externalId);
812+
} else {
813+
$login = new ExternalLogin($user, $service, $externalId);
814+
}
815+
816+
$this->externalLogins->persist($login);
817+
}
818+
819+
$this->sendSuccessResponse($this->userViewFactory->getUser($user));
820+
}
821+
822+
public function checkRemoveExternalLogin(string $id, string $service)
823+
{
824+
$user = $this->users->findOrThrow($id);
825+
if (!$this->userAcl->canSetExternalIds($user)) {
826+
throw new ForbiddenRequestException();
827+
}
828+
829+
// in the future, we might consider cross-checking the service ID
830+
}
831+
832+
/**
833+
* Remove external ID of given authentication service.
834+
* @DELETE
835+
* @param string $id identifier of the user
836+
* @param string $service identifier of the authentication service (login type)
837+
*/
838+
public function actionRemoveExternalLogin(string $id, string $service)
839+
{
840+
$user = $this->users->findOrThrow($id);
841+
$login = $this->externalLogins->findByUser($user, $service);
842+
if ($login) {
843+
$this->externalLogins->remove($login);
844+
}
845+
$this->sendSuccessResponse($this->userViewFactory->getUser($user));
846+
}
767847
}

app/V1Module/router/RouterFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,8 @@ private static function createUsersRoutes(string $prefix): RouteList
493493
$router[] = new PostRoute("$prefix/<id>/create-local", "Users:createLocalAccount");
494494
$router[] = new PostRoute("$prefix/<id>/role", "Users:setRole");
495495
$router[] = new PostRoute("$prefix/<id>/allowed", "Users:setAllowed");
496+
$router[] = new PostRoute("$prefix/<id>/external-login/<service>", "Users:updateExternalLogin");
497+
$router[] = new DeleteRoute("$prefix/<id>/external-login/<service>", "Users:removeExternalLogin");
496498
$router[] = new GetRoute("$prefix/<id>/calendar-tokens", "UserCalendars:userCalendars");
497499
$router[] = new PostRoute("$prefix/<id>/calendar-tokens", "UserCalendars:createCalendar");
498500
$router[] = new GetRoute("$prefix/<id>/pending-reviews", "AssignmentSolutionReviews:pending");

app/V1Module/security/ACL/IUserPermissions.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public function canSetRole(User $user): bool;
3636

3737
public function canSetIsAllowed(User $user): bool;
3838

39+
public function canSetExternalIds(User $user): bool;
40+
3941
public function canInvalidateTokens(User $user): bool;
4042

4143
public function canForceChangePassword(User $user): bool;

app/config/permissions.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ permissions:
400400
- updateProfile
401401
- updatePersonalData
402402
- setIsAllowed
403+
- setExternalIds
403404
- createLocalAccount
404405
- invalidateTokens
405406

app/helpers/FailureHelper/FailureHelper.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
class FailureHelper
1717
{
18-
1918
public const TYPE_BACKEND_ERROR = "BACKEND ERROR";
2019
public const TYPE_API_ERROR = "API ERROR";
2120

app/model/entity/ExternalLogin.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
/**
88
* @ORM\Entity
9-
* @ORM\Table(uniqueConstraints={@ORM\UniqueConstraint(columns={"auth_service", "external_id"})})
9+
* @ORM\Table(uniqueConstraints={@ORM\UniqueConstraint(columns={"auth_service", "user_id"}),
10+
* @ORM\UniqueConstraint(columns={"auth_service", "external_id"})})
1011
*/
1112
class ExternalLogin
1213
{
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Migrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20250209140028 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return '';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
// this up() migration is auto-generated, please modify it to your needs
23+
$this->addSql('CREATE UNIQUE INDEX UNIQ_9845B893AFB9BA21A76ED395 ON external_login (auth_service, user_id)');
24+
}
25+
26+
public function down(Schema $schema): void
27+
{
28+
// this down() migration is auto-generated, please modify it to your needs
29+
$this->addSql('DROP INDEX UNIQ_9845B893AFB9BA21A76ED395 ON external_login');
30+
}
31+
}

0 commit comments

Comments
 (0)