mail-account-admin/src/Frontend/Accounts/AccountHandler.php

291 lines
10 KiB
PHP

<?php
declare(strict_types=1);
namespace MailAccountAdmin\Frontend\Accounts;
use MailAccountAdmin\Common\PasswordHelper;
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, DomainRepository $domainRepository,
PasswordHelper $passwordHelper)
{
$this->accountRepository = $accountRepository;
$this->aliasRepository = $aliasRepository;
$this->domainRepository = $domainRepository;
$this->passwordHelper = $passwordHelper;
}
// -- /accounts - List all accounts
public function listAccounts(string $filterByDomain): array
{
$accountList = $this->accountRepository->fetchAccountList($filterByDomain);
return [
'filterDomain' => $filterByDomain,
'accountList' => $accountList,
];
}
// -- /accounts/{id} - Show account details
public function getAccountDetails(int $accountId): array
{
// Get account data from database
$account = $this->accountRepository->fetchAccountById($accountId);
// Don't display the password hash, but at least the type of hash (used hash algorithm)
$passwordHashType = $this->passwordHelper->getPasswordHashType($account->getPasswordHash());
// Get list of aliases for this account
$aliases = $this->aliasRepository->fetchAliasesForUserId($accountId);
return [
'id' => $accountId,
'accountUsername' => $account->getUsername(),
'account' => $account,
'passwordHashType' => $passwordHashType,
'aliases' => $aliases,
];
}
// -- /accounts/new - Create new account
public function getPageDataForCreate(): array
{
return [
'domainList' => array_keys($this->domainRepository->fetchDomainList()),
];
}
public function createNewAccount(AccountCreateData $createData): array
{
// 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.");
}
// Array returned by the function on success
$returnData = [
'username' => $username,
];
// Hash new password
$password = $createData->getPassword();
if ($password === null) {
$password = $this->passwordHelper->generateRandomPassword();
$returnData['generatedPassword'] = $password;
}
$passwordHash = $this->passwordHelper->hashPassword($password);
// 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
$returnData['id'] = $this->accountRepository->insertAccount(
$username,
$passwordHash,
$createData->getActive(),
$homeDir,
$createData->getMemo()
);
return $returnData;
}
// -- /accounts/{id}/edit - Edit account data
public function getAccountDataForEdit(int $accountId): array
{
// Get account data from database
$account = $this->accountRepository->fetchAccountById($accountId);
return [
'id' => $accountId,
'accountUsername' => $account->getUsername(),
'account' => $account,
];
}
public function editAccountData(int $accountId, AccountEditData $editData): array
{
// Array returned by the function on success
$returnData = [];
// Check if account exists
try {
$account = $this->accountRepository->fetchAccountById($accountId);
} catch (AccountNotFoundException $e) {
throw new InputValidationError('Account with ID ' . $accountId . ' does not exist!');
}
$newUsername = $editData->getUsername();
if ($newUsername === $account->getUsername()) {
// Username is unchanged
$newUsername = null;
}
// This variable will be set to true if the user wants to change their username, has an existing alias with this username,
// and checked the "replace existing alias" option.
$aliasNeedsToBeReplaced = false;
// Check if new username is still available
if ($newUsername !== null) {
$newUsernameAvailable = true;
// Check if account with this username already exists
if (!$this->accountRepository->checkUsernameAvailable($newUsername)) {
$newUsernameAvailable = false;
}
// Check if alias with this username/address already exists
if (!$this->aliasRepository->checkAliasAvailable($newUsername)) {
$newUsernameAvailable = false;
// Alias already exists. If user wants to replace an existing alias, check if the alias belongs to this user.
if ($editData->getUsernameReplaceAlias()) {
$existingAlias = $this->aliasRepository->fetchAliasByAddress($newUsername);
if ($existingAlias->getUserId() === $accountId) {
$aliasNeedsToBeReplaced = true;
$newUsernameAvailable = true;
}
}
}
if (!$newUsernameAvailable) {
throw new InputValidationError("Username \"$newUsername\" is not available.");
}
}
// Start database transaction
$this->accountRepository->beginTransaction();
// Create alias for old username (if wanted)
if ($editData->getUsernameCreateAlias()) {
$oldUsername = $account->getUsername();
if (!$this->aliasRepository->checkAliasAvailable($oldUsername)) {
throw new InputValidationError("Alias \"$oldUsername\" cannot be created: Alias already exists.");
}
$this->aliasRepository->createNewAlias($accountId, $oldUsername);
}
// Generate new password (if wanted)
$newPassword = $editData->getPassword();
if ($editData->getPasswordGenerateRandom()) {
$newPassword = $this->passwordHelper->generateRandomPassword();
$returnData['generatedPassword'] = $newPassword;
}
// Hash new password
$newPasswordHash = $newPassword !== null ? $this->passwordHelper->hashPassword($newPassword) : null;
// Update account in database
$this->accountRepository->updateAccountWithId(
$accountId,
$newUsername,
$newPasswordHash,
$editData->getActive(),
$editData->getHomeDir(),
$editData->getMemo()
);
// Remove existing alias for new username (if wanted)
if ($editData->getUsernameReplaceAlias() && $aliasNeedsToBeReplaced) {
$this->aliasRepository->deleteAlias($accountId, $newUsername);
}
// Commit database transaction
$this->accountRepository->commitTransaction();
return $returnData;
}
// -- /accounts/{id}/delete - Delete account
public function getAccountDataForDelete(int $accountId): array
{
// Get account data from database
$account = $this->accountRepository->fetchAccountById($accountId);
// Get list of aliases for this account
$aliases = $this->aliasRepository->fetchAliasesForUserId($accountId);
return [
'id' => $accountId,
'accountUsername' => $account->getUsername(),
'aliases' => $aliases,
];
}
public function deleteAccount(int $accountId): array
{
// Check if account exists
try {
$account = $this->accountRepository->fetchAccountById($accountId);
} catch (AccountNotFoundException $e) {
throw new InputValidationError('Account with ID ' . $accountId . ' does not exist!');
}
// Start database transaction
$this->accountRepository->beginTransaction();
// Delete all aliases associated with this account
$deleteAliasCount = $this->aliasRepository->deleteAllAliasesForUserId($accountId);
// Delete account from database
$this->accountRepository->deleteAccountWithId($accountId);
// Commit database transaction
$this->accountRepository->commitTransaction();
return [
'username' => $account->getUsername(),
'deleted_alias_count' => $deleteAliasCount,
];
}
// -- /accounts/{id}/addalias - Create a new alias for the account
public function addAliasToAccount(int $accountId, AccountAddAliasData $aliasAddData): void
{
// Check if account exists
try {
$this->accountRepository->fetchAccountById($accountId);
} catch (AccountNotFoundException $e) {
throw new InputValidationError('Account with ID ' . $accountId . ' does not exist!');
}
// Check if alias address is still available
$address = $aliasAddData->getAliasAddress();
if (!$this->accountRepository->checkUsernameAvailable($address) || !$this->aliasRepository->checkAliasAvailable($address)) {
throw new InputValidationError("Alias address \"$address\" is not available.");
}
// Create alias in database
$this->aliasRepository->createNewAlias($accountId, $address);
}
}