Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 60 additions & 14 deletions app/V1Module/presenters/RegistrationPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\V1Module\Presenters;

use App\Helpers\MetaFormats\Attributes\Post;
use App\Helpers\MetaFormats\Validators\VBool;
use App\Helpers\MetaFormats\Validators\VEmail;
use App\Helpers\MetaFormats\Validators\VMixed;
use App\Helpers\MetaFormats\Validators\VString;
Expand Down Expand Up @@ -162,22 +163,36 @@ public function checkCreateAccount()
#[Post("instanceId", new VString(1), "Identifier of the instance to register in")]
#[Post("titlesBeforeName", new VString(1), "Titles which is placed before user name", required: false)]
#[Post("titlesAfterName", new VString(1), "Titles which is placed after user name", required: false)]
#[Post(
"ignoreNameCollision",
new VBool(),
"If a use with the same name exists, this needs to be set to true.",
required: false
)]
public function actionCreateAccount()
{
$req = $this->getRequest();

// check if the email is free
$email = trim($req->getPost("email"));
// username is name of column which holds login identifier represented by email
if ($this->logins->getByUsername($email) !== null) {
throw new BadRequestException("This email address is already taken.");
if ($this->logins->getByUsername($email) !== null || $this->users->getByEmail($email) !== null) {
throw new BadRequestException(
"This email address is already taken.",
FrontendErrorMappings::E400_110__USER_EMAIL_ALREADY_EXISTS
);
}

$instanceId = $req->getPost("instanceId");
$instance = $this->getInstance($instanceId);

$titlesBeforeName = $req->getPost("titlesBeforeName") === null ? "" : $req->getPost("titlesBeforeName");
$titlesAfterName = $req->getPost("titlesAfterName") === null ? "" : $req->getPost("titlesAfterName");
$titlesBeforeName = trim($req->getPost("titlesBeforeName") ?? "");
$titlesAfterName = trim($req->getPost("titlesAfterName") ?? "");
$firstName = trim($req->getPost("firstName") ?? "");
$lastName = trim($req->getPost("lastName") ?? "");
if (!$firstName || !$lastName) {
throw new BadRequestException("The user's full name must be filled in.");
}

// check given passwords
$password = $req->getPost("password");
Expand All @@ -189,10 +204,23 @@ public function actionCreateAccount()
);
}

// Check for name collisions, unless the request explicitly says to ignore them.
if (!$req->getPost("ignoreNameCollision")) {
$sameName = $this->users->findByName($instance, $firstName, $lastName);
if ($sameName) {
// let's report the colliding users
$this->sendSuccessResponse([
"user" => null,
"usersWithSameName" => $this->userViewFactory->getUsers($sameName),
]);
return;
}
}

$user = new User(
$email,
$req->getPost("firstName"),
$req->getPost("lastName"),
$firstName,
$lastName,
$titlesBeforeName,
$titlesAfterName,
Roles::STUDENT_ROLE,
Expand Down Expand Up @@ -272,31 +300,49 @@ public function actionCreateInvitation()
// check if the email is free
$email = trim($format->email);
// username is name of column which holds login identifier represented by email
if ($this->logins->getByUsername($email) !== null) {
throw new BadRequestException("This email address is already taken.");
if ($this->logins->getByUsername($email) !== null || $this->users->getByEmail($email) !== null) {
throw new BadRequestException(
"This email address is already taken.",
FrontendErrorMappings::E400_110__USER_EMAIL_ALREADY_EXISTS
);
}

$groupsIds = $format->groups ?? [];
foreach ($groupsIds as $id) {
$group = $this->groups->get($id);
if (!$group || $group->isOrganizational() || !$this->groupAcl->canInviteStudents($group)) {
throw new BadRequestException("Current user cannot invite people in group '$id'");
throw new ForbiddenRequestException("Current user cannot invite people in group '$id'");
}
}

// gather data
$instanceId = $format->instanceId;
$instance = $this->getInstance($instanceId);
$titlesBeforeName = $format->titlesBeforeName === null ? "" : $format->titlesBeforeName;
$titlesAfterName = $format->titlesAfterName === null ? "" : $format->titlesAfterName;
$instance = $this->getInstance($instanceId); // we don't need it, just to check it exists
$titlesBeforeName = $format->titlesBeforeName === null ? "" : trim($format->titlesBeforeName);
$titlesAfterName = $format->titlesAfterName === null ? "" : trim($format->titlesAfterName);
$firstName = trim($format->firstName);
$lastName = trim($format->lastName);
if (!$firstName || !$lastName) {
throw new BadRequestException("The user's full name must be filled in.");
}

// Check for name collisions, unless the request explicitly says to ignore them.
if (!$format->ignoreNameCollision) {
$sameName = $this->users->findByName($instance, $firstName, $lastName);
if ($sameName) {
// let's report the colliding users
$this->sendSuccessResponse($this->userViewFactory->getUsers($sameName));
return;
}
}

// create the token and send it via email
try {
$this->invitationHelper->invite(
$instanceId,
$email,
$format->firstName,
$format->lastName,
$firstName,
$lastName,
$titlesBeforeName,
$titlesAfterName,
$groupsIds,
Expand Down
2 changes: 2 additions & 0 deletions app/exceptions/FrontendErrorMappings.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class FrontendErrorMappings
public const E400_104__EXTERNAL_AUTH_FAILED_USER_NOT_FOUND = "400-104";
/** External authentication failed - unable to register new user because no role was provided. */
public const E400_105__EXTERNAL_AUTH_FAILED_MISSING_ROLE = "400-105";
/** User email already exists, new user cannot be created/invited/registered/.... */
public const E400_110__USER_EMAIL_ALREADY_EXISTS = "400-110";

/** General job config error */
public const E400_200__JOB_CONFIG = "400-200";
Expand Down
6 changes: 4 additions & 2 deletions app/helpers/MetaFormats/FormatDefinitions/UserFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@

use App\Helpers\MetaFormats\Attributes\Format;
use App\Helpers\MetaFormats\MetaFormat;
use App\Helpers\MetaFormats\Attributes\FormatParameterAttribute;
use App\Helpers\MetaFormats\Attributes\FPost;
use App\Helpers\MetaFormats\Type;
use App\Helpers\MetaFormats\Validators\VArray;
use App\Helpers\MetaFormats\Validators\VBool;
use App\Helpers\MetaFormats\Validators\VEmail;
use App\Helpers\MetaFormats\Validators\VString;
use App\Helpers\MetaFormats\Validators\VUuid;
Expand Down Expand Up @@ -41,4 +40,7 @@ class UserFormat extends MetaFormat

#[FPost(new VString(2, 2), "Language used in the invitation email (en by default).", required: false)]
public ?string $locale;

#[FPost(new VBool(), "If a use with the same name exists, this needs to be set to true.", required: false)]
public ?bool $ignoreNameCollision;
}
20 changes: 18 additions & 2 deletions app/model/repository/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Helpers\Pagination;
use App\Model\Helpers\PaginationDbHelper;
use App\Model\Entity\User;
use App\Model\Entity\Instance;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;

Expand Down Expand Up @@ -55,7 +56,7 @@ public function getPaginated(Pagination $pagination, &$totalCount): array
'email' => ['u.email'],
'createdAt' => ['u.createdAt'],
],
['firstName', 'lastName'] // search column names
['firstName', 'lastName', 'email'] // search column names
);
$paginationDbHelper->apply($qb, $pagination);

Expand All @@ -75,7 +76,7 @@ public function getPaginated(Pagination $pagination, &$totalCount): array


/**
* Search users first names and surnames based on given string.
* Search users first names and surnames based on given (sub)string.
* @param string|null $search
* @return User[]
*/
Expand All @@ -93,6 +94,21 @@ public function findByRoles(string ...$roles): array
return $this->findBy(["role" => $roles]);
}

/**
* Find users by exact match of the whole name.
* @param Instance $instance
* @param string $firstName
* @param string $lastName
* @return User[]
*/
public function findByName(Instance $instance, string $firstName, string $lastName): array
{
$users = $this->findBy(["firstName" => $firstName, "lastName" => $lastName]);
return array_filter($users, function (User $user) use ($instance) {
return $user->belongsTo($instance);
});
}

/**
* Find all users who have not authenticated to the system for some time.
* @param DateTime|null $before Only users with last activity before given date
Expand Down
Loading