Implement account creation; add base class FormData to validate forms
This commit is contained in:
parent
23127dd193
commit
48925f283f
13 changed files with 392 additions and 96 deletions
|
|
@ -52,8 +52,39 @@ class AccountController extends BaseController
|
|||
|
||||
public function showAccountCreate(Request $request, Response $response): Response
|
||||
{
|
||||
// TODO: just a placeholder
|
||||
return $this->showAccounts($request, $response);
|
||||
$renderData = $this->accountHandler->getPageDataForCreate();
|
||||
|
||||
// If the form has been submitted, add the result message and form input data to the render data array
|
||||
$lastActionResult = $this->sessionHelper->getLastActionResult();
|
||||
if ($lastActionResult !== null) {
|
||||
$renderData = array_merge($renderData, $lastActionResult->getRenderData());
|
||||
}
|
||||
|
||||
return $this->view->render($response, 'account_create.html.twig', $renderData);
|
||||
}
|
||||
|
||||
public function createAccount(Request $request, Response $response): Response
|
||||
{
|
||||
// Parse form data
|
||||
$createData = $request->getParsedBody();
|
||||
|
||||
try {
|
||||
// Validate input
|
||||
$validatedCreateData = AccountCreateData::createFromArray($createData);
|
||||
$newAccountId = $this->accountHandler->createNewAccount($validatedCreateData);
|
||||
|
||||
// Save success result
|
||||
$newAccountName = $validatedCreateData->getUsername();
|
||||
$this->sessionHelper->setLastActionResult(ActionResult::createSuccessResult(
|
||||
'Account <a href="/accounts/' . $newAccountId . '">' . $newAccountName . '</a> was created.'
|
||||
));
|
||||
} catch (InputValidationError $e) {
|
||||
// Save error result
|
||||
$this->sessionHelper->setLastActionResult(ActionResult::createErrorResult($e->getMessage(), $createData));
|
||||
}
|
||||
|
||||
// Redirect to edit form page via GET (PRG)
|
||||
return $response->withHeader('Location', '/accounts/new')->withStatus(303);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -78,25 +109,20 @@ class AccountController extends BaseController
|
|||
|
||||
public function editAccount(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
// Parse URL arguments
|
||||
// Parse URL arguments and form data
|
||||
$accountId = (int)$args['id'];
|
||||
|
||||
// Parse form data
|
||||
$editData = $request->getParsedBody();
|
||||
$errorMessage = null;
|
||||
|
||||
try {
|
||||
// Validate input
|
||||
$validatedEditData = AccountEditData::createFromArray($editData);
|
||||
$this->accountHandler->editAccountData($accountId, $validatedEditData);
|
||||
} catch (InputValidationError $e) {
|
||||
$errorMessage = $e->getMessage();
|
||||
}
|
||||
|
||||
if (empty($errorMessage)) {
|
||||
// Save success result
|
||||
$this->sessionHelper->setLastActionResult(ActionResult::createSuccessResult('Account data was saved.'));
|
||||
} else {
|
||||
$this->sessionHelper->setLastActionResult(ActionResult::createErrorResult($errorMessage, $editData));
|
||||
} catch (InputValidationError $e) {
|
||||
// Save error result
|
||||
$this->sessionHelper->setLastActionResult(ActionResult::createErrorResult($e->getMessage(), $editData));
|
||||
}
|
||||
|
||||
// Redirect to edit form page via GET (PRG)
|
||||
|
|
|
|||
60
src/Frontend/Accounts/AccountCreateData.php
Normal file
60
src/Frontend/Accounts/AccountCreateData.php
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MailAccountAdmin\Frontend\Accounts;
|
||||
|
||||
use MailAccountAdmin\Common\FormData;
|
||||
|
||||
class AccountCreateData extends FormData
|
||||
{
|
||||
private string $username;
|
||||
private string $password;
|
||||
private bool $active;
|
||||
private ?string $homeDir;
|
||||
private string $memo;
|
||||
|
||||
private function __construct(string $username, string $password, bool $active, ?string $homeDir, string $memo)
|
||||
{
|
||||
$this->username = $username;
|
||||
$this->password = $password;
|
||||
$this->active = $active;
|
||||
$this->homeDir = $homeDir;
|
||||
$this->memo = $memo;
|
||||
}
|
||||
|
||||
public static function createFromArray($raw): self
|
||||
{
|
||||
return new self(
|
||||
self::validateUsername(trim($raw['username'] ?? '')),
|
||||
self::validatePassword(trim($raw['password'] ?? ''), trim($raw['password_repeat'] ?? '')),
|
||||
self::validateBoolOption(trim($raw['is_active'] ?? '')),
|
||||
self::validateHomeDir(trim($raw['home_dir'] ?? ''), false),
|
||||
self::validateMemo(trim($raw['memo'] ?? '')),
|
||||
);
|
||||
}
|
||||
|
||||
public function getUsername(): string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function getActive(): bool
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
public function getHomeDir(): ?string
|
||||
{
|
||||
return $this->homeDir;
|
||||
}
|
||||
|
||||
public function getMemo(): string
|
||||
{
|
||||
return $this->memo;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace MailAccountAdmin\Frontend\Accounts;
|
||||
|
||||
use MailAccountAdmin\Exceptions\InputValidationError;
|
||||
use MailAccountAdmin\Common\FormData;
|
||||
|
||||
class AccountEditData
|
||||
class AccountEditData extends FormData
|
||||
{
|
||||
private ?string $username;
|
||||
private bool $usernameCreateAlias;
|
||||
|
|
@ -13,10 +13,10 @@ class AccountEditData
|
|||
private ?string $password;
|
||||
private bool $active;
|
||||
private ?string $homeDir;
|
||||
private ?string $memo;
|
||||
private string $memo;
|
||||
|
||||
private function __construct(?string $username, bool $usernameCreateAlias, bool $usernameReplaceAlias, ?string $password, bool $active,
|
||||
?string $homeDir, ?string $memo)
|
||||
?string $homeDir, string $memo)
|
||||
{
|
||||
$this->username = $username;
|
||||
$this->usernameCreateAlias = $usernameCreateAlias;
|
||||
|
|
@ -30,70 +30,16 @@ class AccountEditData
|
|||
public static function createFromArray($raw): self
|
||||
{
|
||||
return new self(
|
||||
self::validateUsername(trim($raw['username'] ?? '')),
|
||||
self::validateUsername(trim($raw['username'] ?? ''), false),
|
||||
self::validateBoolOption(trim($raw['username_create_alias'] ?? '')),
|
||||
self::validateBoolOption(trim($raw['username_replace_alias'] ?? '')),
|
||||
self::validatePassword(trim($raw['password'] ?? ''), trim($raw['password_repeat'] ?? '')),
|
||||
self::validatePassword(trim($raw['password'] ?? ''), trim($raw['password_repeat'] ?? ''), false),
|
||||
self::validateBoolOption(trim($raw['is_active'] ?? '')),
|
||||
self::validateHomeDir(trim($raw['home_dir'] ?? '')),
|
||||
trim($raw['memo'] ?? '')
|
||||
self::validateHomeDir(trim($raw['home_dir'] ?? ''), false),
|
||||
self::validateMemo(trim($raw['memo'] ?? '')),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Input validation
|
||||
|
||||
private static function validateUsername(string $username): ?string
|
||||
{
|
||||
if ($username === '') {
|
||||
return null;
|
||||
}
|
||||
if (strlen($username) > 100) {
|
||||
throw new InputValidationError('Username is too long.');
|
||||
}
|
||||
|
||||
$username = strtolower($username);
|
||||
if (!preg_match('/^[a-z0-9._+-]+@[a-z0-9.-]+$/', $username) || preg_match('/^\\.|\\.\\.|\\.@|@\\.|\\.$/', $username)) {
|
||||
throw new InputValidationError('Username is not valid (must be a valid mail address).');
|
||||
}
|
||||
return $username;
|
||||
}
|
||||
|
||||
private static function validatePassword(string $password, string $passwordRepeat): ?string
|
||||
{
|
||||
if ($password === '') {
|
||||
return null;
|
||||
}
|
||||
if ($password !== $passwordRepeat) {
|
||||
throw new InputValidationError('Passwords do not match.');
|
||||
}
|
||||
return $password;
|
||||
}
|
||||
|
||||
private static function validateHomeDir(string $homeDir): ?string
|
||||
{
|
||||
if ($homeDir === '') {
|
||||
return null;
|
||||
}
|
||||
if (strlen($homeDir) > 100) {
|
||||
throw new InputValidationError('Home directory is too long.');
|
||||
}
|
||||
|
||||
$homeDir = trim($homeDir, '/');
|
||||
if (!preg_match('!^[a-z0-9._+-]+(/[a-z0-9._+-]+)*$!i', $homeDir)) {
|
||||
throw new InputValidationError('Home directory is not a valid path.');
|
||||
}
|
||||
return $homeDir;
|
||||
}
|
||||
|
||||
private static function validateBoolOption(string $raw): bool
|
||||
{
|
||||
return $raw !== '';
|
||||
}
|
||||
|
||||
|
||||
// Getters
|
||||
|
||||
public function getUsername(): ?string
|
||||
{
|
||||
return $this->username;
|
||||
|
|
|
|||
|
|
@ -8,17 +8,21 @@ use MailAccountAdmin\Exceptions\AccountNotFoundException;
|
|||
use MailAccountAdmin\Exceptions\InputValidationError;
|
||||
use MailAccountAdmin\Repositories\AccountRepository;
|
||||
use MailAccountAdmin\Repositories\AliasRepository;
|
||||
use MailAccountAdmin\Repositories\DomainRepository;
|
||||
|
||||
class AccountHandler
|
||||
{
|
||||
private AccountRepository $accountRepository;
|
||||
private AliasRepository $aliasRepository;
|
||||
private DomainRepository $domainRepository;
|
||||
private PasswordHelper $passwordHelper;
|
||||
|
||||
public function __construct(AccountRepository $accountRepository, AliasRepository $aliasRepository, PasswordHelper $passwordHelper)
|
||||
public function __construct(AccountRepository $accountRepository, AliasRepository $aliasRepository, DomainRepository $domainRepository,
|
||||
PasswordHelper $passwordHelper)
|
||||
{
|
||||
$this->accountRepository = $accountRepository;
|
||||
$this->aliasRepository = $aliasRepository;
|
||||
$this->domainRepository = $domainRepository;
|
||||
$this->passwordHelper = $passwordHelper;
|
||||
}
|
||||
|
||||
|
|
@ -59,6 +63,45 @@ class AccountHandler
|
|||
}
|
||||
|
||||
|
||||
// -- /accounts/new - Create new account
|
||||
|
||||
public function getPageDataForCreate(): array
|
||||
{
|
||||
return [
|
||||
'domainList' => array_keys($this->domainRepository->fetchDomainList()),
|
||||
];
|
||||
}
|
||||
|
||||
public function createNewAccount(AccountCreateData $createData): int
|
||||
{
|
||||
// Check if new username is still available
|
||||
$username = $createData->getUsername();
|
||||
if (!$this->accountRepository->checkUsernameAvailable($username) || !$this->aliasRepository->checkAliasAvailable($username)) {
|
||||
throw new InputValidationError("Username \"$username\" is not available.");
|
||||
}
|
||||
|
||||
// Hash new password
|
||||
$passwordHash = $this->passwordHelper->hashPassword($createData->getPassword());
|
||||
|
||||
// Construct home directory from username if necessary
|
||||
if ($createData->getHomeDir() !== null) {
|
||||
$homeDir = $createData->getHomeDir();
|
||||
} else {
|
||||
[$localPart, $domainPart] = explode('@', $username, 2);
|
||||
$homeDir = $domainPart . '/' . $localPart;
|
||||
}
|
||||
|
||||
// Create account in database
|
||||
return $this->accountRepository->insertAccount(
|
||||
$username,
|
||||
$passwordHash,
|
||||
$createData->getActive(),
|
||||
$homeDir,
|
||||
$createData->getMemo()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// -- /accounts/{id}/edit - Edit account data
|
||||
|
||||
public function getAccountDataForEdit(int $accountId): array
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue