-
Notifications
You must be signed in to change notification settings - Fork 276
Fix user add and update api endpoints #3040
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
<?php declare(strict_types=1); | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Controller\API; | ||
|
||
|
@@ -23,7 +25,10 @@ | |
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; | ||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; | ||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | ||
use Symfony\Component\Security\Http\Attribute\IsGranted; | ||
use Symfony\Component\Validator\Exception\ValidationFailedException; | ||
use Symfony\Component\Validator\Validator\ValidatorInterface; | ||
|
||
/** | ||
* @extends AbstractRestController<User, User> | ||
|
@@ -41,7 +46,8 @@ | |
DOMJudgeService $dj, | ||
ConfigurationService $config, | ||
EventLogService $eventLogService, | ||
protected readonly ImportExportService $importExportService | ||
protected readonly ImportExportService $importExportService, | ||
protected readonly ValidatorInterface $validator, | ||
) { | ||
parent::__construct($entityManager, $dj, $config, $eventLogService); | ||
} | ||
|
@@ -86,9 +92,10 @@ | |
$message = null; | ||
$result = -1; | ||
if ((($tsvFile && ($result = $this->importExportService->importTsv('groups', $tsvFile, $message))) || | ||
($jsonFile && ($result = $this->importExportService->importJson('groups', $jsonFile, $message)))) && | ||
$result >= 0) { | ||
return "$result new group(s) successfully added."; | ||
($jsonFile && ($result = $this->importExportService->importJson('groups', $jsonFile, $message)))) && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you change anything here. Can either first run your linter on the file and commit or remove the changes your linter made (my preference)? |
||
$result >= 0 | ||
) { | ||
return "$result new group(s) successfully added."; | ||
} else { | ||
throw new BadRequestHttpException("Error while adding groups: $message"); | ||
} | ||
|
@@ -110,7 +117,8 @@ | |
property: 'json', | ||
description: 'The organizations.json files to import.', | ||
type: 'string', | ||
format: 'binary'), | ||
format: 'binary' | ||
), | ||
] | ||
) | ||
) | ||
|
@@ -121,7 +129,8 @@ | |
$message = null; | ||
/** @var UploadedFile|null $jsonFile */ | ||
$jsonFile = $request->files->get('json'); | ||
if ($jsonFile && | ||
if ( | ||
$jsonFile && | ||
($result = $this->importExportService->importJson('organizations', $jsonFile, $message)) && | ||
$result >= 0 | ||
) { | ||
|
@@ -171,8 +180,9 @@ | |
} | ||
$message = null; | ||
if ((($tsvFile && ($result = $this->importExportService->importTsv('teams', $tsvFile, $message))) || | ||
($jsonFile && ($result = $this->importExportService->importJson('teams', $jsonFile, $message)))) && | ||
$result >= 0) { | ||
($jsonFile && ($result = $this->importExportService->importJson('teams', $jsonFile, $message)))) && | ||
$result >= 0 | ||
) { | ||
// TODO: better return all teams here? | ||
return "$result new team(s) successfully added."; | ||
} else { | ||
|
@@ -235,8 +245,9 @@ | |
|
||
$message = null; | ||
if ((($tsvFile && ($result = $this->importExportService->importTsv('accounts', $tsvFile, $message))) || | ||
($jsonFile && ($result = $this->importExportService->importJson('accounts', $jsonFile, $message)))) && | ||
$result >= 0) { | ||
($jsonFile && ($result = $this->importExportService->importJson('accounts', $jsonFile, $message)))) && | ||
$result >= 0 | ||
) { | ||
// TODO: better return all accounts here? | ||
return "$result new account(s) successfully added."; | ||
} else { | ||
|
@@ -259,11 +270,13 @@ | |
) | ||
)] | ||
#[OA\Parameter(ref: '#/components/parameters/idlist')] | ||
#[OA\Parameter( | ||
name: 'team_id', | ||
description: 'Only show users for the given team', | ||
in: 'query', | ||
schema: new OA\Schema(type: 'string')) | ||
#[ | ||
OA\Parameter( | ||
name: 'team_id', | ||
description: 'Only show users for the given team', | ||
in: 'query', | ||
schema: new OA\Schema(type: 'string') | ||
) | ||
] | ||
public function listAction(Request $request): Response | ||
{ | ||
|
@@ -291,13 +304,12 @@ | |
* Add a new user. | ||
*/ | ||
#[IsGranted('ROLE_API_WRITER')] | ||
#[Rest\Post] | ||
#[Rest\Post()] | ||
#[OA\RequestBody( | ||
required: true, | ||
content: [ | ||
new OA\MediaType( | ||
mediaType: 'multipart/form-data', | ||
schema: new OA\Schema(ref: new Model(type: AddUser::class)) | ||
new OA\JsonContent( | ||
ref: new Model(type: AddUser::class) | ||
), | ||
] | ||
)] | ||
|
@@ -307,24 +319,23 @@ | |
content: new Model(type: User::class) | ||
)] | ||
public function addAction( | ||
#[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST)] | ||
#[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST, validationGroups: ['add'])] | ||
AddUser $addUser, | ||
Request $request | ||
): Response { | ||
return $this->addOrUpdateUser($addUser, $request); | ||
return $this->addOrUpdateUser($addUser, new User(), $request); | ||
} | ||
|
||
/** | ||
* Update an existing User or create one with the given ID | ||
* Update an existing User | ||
*/ | ||
#[IsGranted('ROLE_API_WRITER')] | ||
#[Rest\Put('/{id}')] | ||
#[Rest\Patch('/{id}')] | ||
#[OA\RequestBody( | ||
required: true, | ||
content: [ | ||
new OA\MediaType( | ||
mediaType: 'multipart/form-data', | ||
schema: new OA\Schema(ref: new Model(type: UpdateUser::class)) | ||
new OA\JsonContent( | ||
ref: new Model(type: UpdateUser::class) | ||
), | ||
] | ||
)] | ||
|
@@ -335,58 +346,55 @@ | |
)] | ||
public function updateAction( | ||
#[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST)] | ||
UpdateUser $updateUser, | ||
UpdateUser $mutateUser, | ||
string $id, | ||
Request $request | ||
): Response { | ||
return $this->addOrUpdateUser($updateUser, $request); | ||
/** @var User|null $user */ | ||
$user = $this->em->getRepository(User::class)->findOneBy(['externalid' => $id]); | ||
if ($user === null) { | ||
throw new NotFoundHttpException(sprintf("User with id %s not found", $id)); | ||
} | ||
return $this->addOrUpdateUser($mutateUser, $user, $request); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name |
||
} | ||
|
||
protected function addOrUpdateUser(AddUser $addUser, Request $request): Response | ||
protected function addOrUpdateUser(AddUser|UpdateUser $mutateUser, User $user, Request $request): Response | ||
{ | ||
if ($addUser instanceof UpdateUser && !$addUser->id) { | ||
throw new BadRequestHttpException('`id` field is required'); | ||
if ($mutateUser->username !== null) { | ||
$user->setUsername($mutateUser->username); | ||
} | ||
|
||
if ($this->em->getRepository(User::class)->findOneBy(['username' => $addUser->username])) { | ||
throw new BadRequestHttpException(sprintf("User %s already exists", $addUser->username)); | ||
if ($mutateUser->name !== null) { | ||
$user->setName($mutateUser->name); | ||
} | ||
|
||
$user = new User(); | ||
if ($addUser instanceof UpdateUser) { | ||
$existingUser = $this->em->getRepository(User::class)->findOneBy(['externalid' => $addUser->id]); | ||
if ($existingUser) { | ||
$user = $existingUser; | ||
} | ||
if ($mutateUser->email !== null) { | ||
$user->setEmail($mutateUser->email); | ||
} | ||
$user | ||
->setUsername($addUser->username) | ||
->setName($addUser->name) | ||
->setEmail($addUser->email) | ||
->setIpAddress($addUser->ip) | ||
->setPlainPassword($addUser->password) | ||
->setEnabled($addUser->enabled ?? true); | ||
|
||
if ($addUser instanceof UpdateUser) { | ||
$user->setExternalid($addUser->id); | ||
if ($mutateUser->ip !== null) { | ||
$user->setIpAddress($mutateUser->ip); | ||
} | ||
|
||
if ($addUser->teamId) { | ||
if ($mutateUser->password !== null) { | ||
$user->setPlainPassword($mutateUser->password); | ||
} | ||
if ($mutateUser->enabled !== null) { | ||
$user->setEnabled($mutateUser->enabled); | ||
} | ||
if ($mutateUser->teamId) { | ||
/** @var Team|null $team */ | ||
$team = $this->em->createQueryBuilder() | ||
->from(Team::class, 't') | ||
->select('t') | ||
->andWhere('t.externalid = :team') | ||
->setParameter('team', $addUser->teamId) | ||
->setParameter('team', $mutateUser->teamId) | ||
->getQuery() | ||
->getOneOrNullResult(); | ||
|
||
if ($team === null) { | ||
throw new BadRequestHttpException(sprintf("Team %s not found", $addUser->teamId)); | ||
throw new BadRequestHttpException(sprintf("Team %s not found", $mutateUser->teamId)); | ||
} | ||
$user->setTeam($team); | ||
} | ||
|
||
$roles = $addUser->roles; | ||
$roles = $mutateUser->roles; | ||
// For the file import we change a CDS user to the roles needed for ICPC CDS. | ||
if ($user->getUsername() === 'cds') { | ||
$roles = ['cds']; | ||
|
@@ -408,18 +416,23 @@ | |
$user->addUserRole($role); | ||
} | ||
|
||
$validation = $this->validator->validate($user); | ||
if (count($validation) > 0) { | ||
throw new ValidationFailedException($user, $validation); | ||
} | ||
|
||
$this->em->persist($user); | ||
$this->em->flush(); | ||
$this->dj->auditlog('user', $user->getUserid(), 'added'); | ||
$this->dj->auditlog('user', $user->getUserid(), 'updated'); | ||
|
||
return $this->renderCreateData($request, $user, 'user', $user->getUserid()); | ||
} | ||
|
||
protected function getQueryBuilder(Request $request): QueryBuilder | ||
{ | ||
$queryBuilder = $this->em->createQueryBuilder() | ||
->from(User::class, 'u') | ||
->select('u'); | ||
->from(User::class, 'u') | ||
->select('u'); | ||
|
||
if ($request->query->has('team')) { | ||
$queryBuilder | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,29 @@ | ||
<?php declare(strict_types=1); | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\DataTransferObject; | ||
|
||
use JMS\Serializer\Annotation as Serializer; | ||
use OpenApi\Attributes as OA; | ||
use Symfony\Component\Validator\Constraints as Assert; | ||
|
||
#[OA\Schema(required: ['username', 'name', 'roles'])] | ||
class AddUser | ||
{ | ||
#[Assert\NotBlank] | ||
public string $username; | ||
#[Assert\NotBlank] | ||
public string $name; | ||
public ?string $id = null; | ||
public ?string $email = null; | ||
public ?string $ip = null; | ||
#[OA\Property(format: 'password')] | ||
public ?string $password = null; | ||
public ?bool $enabled = null; | ||
public ?string $teamId = null; | ||
/** | ||
* @param array<string> $roles | ||
* @var array<string> | ||
LuukBlankenstijn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
public function __construct( | ||
public readonly string $username, | ||
public readonly string $name, | ||
#[OA\Property(format: 'email', nullable: true)] | ||
public readonly ?string $email, | ||
#[OA\Property(nullable: true)] | ||
public readonly ?string $ip, | ||
#[OA\Property(format: 'password', nullable: true)] | ||
public readonly ?string $password, | ||
#[OA\Property(nullable: true)] | ||
public readonly ?bool $enabled, | ||
#[OA\Property(nullable: true)] | ||
public readonly ?string $teamId, | ||
#[Serializer\Type('array<string>')] | ||
public readonly array $roles, | ||
) {} | ||
#[Serializer\Type('array<string>')] | ||
public array $roles = []; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,26 @@ | ||
<?php declare(strict_types=1); | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\DataTransferObject; | ||
|
||
use JMS\Serializer\Annotation as Serializer; | ||
use OpenApi\Attributes as OA; | ||
|
||
#[OA\Schema(required: ['id', 'username', 'name', 'roles'])] | ||
class UpdateUser extends AddUser | ||
class UpdateUser | ||
{ | ||
public function __construct( | ||
public readonly string $id, | ||
string $username, | ||
string $name, | ||
?string $email, | ||
?string $ip, | ||
?string $password, | ||
?bool $enabled, | ||
?string $teamId, | ||
array $roles | ||
) { | ||
parent::__construct($username, $name, $email, $ip, $password, $enabled, $teamId, $roles); | ||
} | ||
public ?string $id = null; | ||
public ?string $username = null; | ||
public ?string $name = null; | ||
public ?string $email = null; | ||
public ?string $ip = null; | ||
#[OA\Property(format: 'password')] | ||
public ?string $password = null; | ||
public ?bool $enabled = null; | ||
public ?string $teamId = null; | ||
/** | ||
* @var array<string> | ||
*/ | ||
#[Serializer\Type('array<string>')] | ||
public array $roles = []; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason you wanted to change this? I don't think we want the newlines there.