mirror of
https://github.com/getnamingo/registry.git
synced 2025-05-13 16:16:59 +02:00
420 lines
No EOL
19 KiB
PHP
420 lines
No EOL
19 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers;
|
|
|
|
use App\Models\User;
|
|
use Psr\Http\Message\ResponseInterface as Response;
|
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
|
use Psr\Container\ContainerInterface;
|
|
use Respect\Validation\Validator as v;
|
|
|
|
class UsersController extends Controller
|
|
{
|
|
public function listUsers(Request $request, Response $response)
|
|
{
|
|
if ($_SESSION["auth_roles"] != 0) {
|
|
return $response->withHeader('Location', '/dashboard')->withStatus(302);
|
|
}
|
|
|
|
$userModel = new User($this->container->get('db'));
|
|
$users = $userModel->getAllUsers();
|
|
return view($response,'admin/users/listUsers.twig', compact('users'));
|
|
}
|
|
|
|
public function createUser(Request $request, Response $response)
|
|
{
|
|
// Registrars can not create new users, then need to ask the registry
|
|
if ($_SESSION["auth_roles"] != 0) {
|
|
return $response->withHeader('Location', '/dashboard')->withStatus(302);
|
|
}
|
|
|
|
if ($request->getMethod() === 'POST') {
|
|
// Retrieve POST data
|
|
$data = $request->getParsedBody();
|
|
$db = $this->container->get('db');
|
|
$email = $data['email'] ?? null;
|
|
$username = $data['username'] ?? null;
|
|
$password = $data['password'] ?? null;
|
|
$password_confirmation = $data['password_confirmation'] ?? null;
|
|
$status = $data['status'] ?? null;
|
|
$verified = $data['verified'] ?? null;
|
|
$role = $data['role'] ?? null;
|
|
$registrar_id = $data['registrar_id'] ?? null;
|
|
|
|
// Define validation rules
|
|
$validators = [
|
|
'email' => v::email()->notEmpty()->setName('Email'),
|
|
'username' => v::regex('/^[a-zA-Z0-9_-]+$/')->length(3, 20)->setName('Username'),
|
|
'password' => v::stringType()->notEmpty()->length(6, 255)->setName('Password'),
|
|
'password_confirmation' => v::equals($data['password'] ?? '')->setName('Password Confirmation'),
|
|
'status' => v::in(['0', '4'])->setName('Status'),
|
|
'role' => v::in(['admin', 'registrar'])->setName('Role'),
|
|
];
|
|
|
|
// Add registrar_id validation if role is registrar
|
|
if (($data['role'] ?? '') === 'registrar') {
|
|
$validators['registrar_id'] = v::numericVal()->notEmpty()->setName('Registrar ID');
|
|
}
|
|
|
|
// Validate data
|
|
$errors = [];
|
|
foreach ($validators as $field => $validator) {
|
|
try {
|
|
$validator->assert($data[$field] ?? null);
|
|
} catch (\Respect\Validation\Exceptions\ValidationException $exception) {
|
|
$errors[$field] = $exception->getMessages(); // Collect all error messages
|
|
}
|
|
}
|
|
|
|
// If errors exist, return with errors
|
|
if (!empty($errors)) {
|
|
// Flatten the errors array into a string
|
|
$errorMessages = [];
|
|
foreach ($errors as $field => $fieldErrors) {
|
|
$fieldMessages = implode(', ', $fieldErrors); // Concatenate messages for the field
|
|
$errorMessages[] = ucfirst($field) . ': ' . $fieldMessages; // Prefix with field name
|
|
}
|
|
$errorString = implode('; ', $errorMessages); // Join all fields' errors
|
|
|
|
// Add the flattened error string as a flash message
|
|
$this->container->get('flash')->addMessage('error', 'Error: ' . $errorString);
|
|
|
|
// Redirect back to the form
|
|
return $response->withHeader('Location', '/user/create')->withStatus(302);
|
|
}
|
|
|
|
if (!checkPasswordComplexity($password)) {
|
|
$this->container->get('flash')->addMessage('error', 'Password too weak. Use a stronger password');
|
|
return $response->withHeader('Location', '/user/create')->withStatus(302);
|
|
}
|
|
|
|
$registrars = $db->select("SELECT id, clid, name FROM registrar");
|
|
if ($_SESSION["auth_roles"] != 0) {
|
|
$registrar = true;
|
|
} else {
|
|
$registrar = null;
|
|
}
|
|
|
|
if ($email) {
|
|
if ($registrar_id) {
|
|
$db->beginTransaction();
|
|
|
|
$password_hashed = password_hash($password, PASSWORD_ARGON2ID, ['memory_cost' => 1024 * 128, 'time_cost' => 6, 'threads' => 4]);
|
|
|
|
try {
|
|
$db->insert(
|
|
'users',
|
|
[
|
|
'email' => $email,
|
|
'password' => $password_hashed,
|
|
'username' => $username,
|
|
'verified' => $verified,
|
|
'roles_mask' => 4,
|
|
'status' => $status,
|
|
'registered' => \time()
|
|
]
|
|
);
|
|
$user_id = $db->getLastInsertId();
|
|
|
|
$db->insert(
|
|
'registrar_users',
|
|
[
|
|
'registrar_id' => $registrar_id,
|
|
'user_id' => $user_id
|
|
]
|
|
);
|
|
|
|
$db->commit();
|
|
} catch (Exception $e) {
|
|
$db->rollBack();
|
|
$this->container->get('flash')->addMessage('error', 'Database failure: ' . $e->getMessage());
|
|
return $response->withHeader('Location', '/user/create')->withStatus(302);
|
|
}
|
|
|
|
$this->container->get('flash')->addMessage('success', 'User ' . $email . ' has been created successfully');
|
|
return $response->withHeader('Location', '/users')->withStatus(302);
|
|
} else {
|
|
$db->beginTransaction();
|
|
|
|
$password_hashed = password_hash($password, PASSWORD_ARGON2ID, ['memory_cost' => 1024 * 128, 'time_cost' => 6, 'threads' => 4]);
|
|
|
|
try {
|
|
$db->insert(
|
|
'users',
|
|
[
|
|
'email' => $email,
|
|
'password' => $password_hashed,
|
|
'username' => $username,
|
|
'verified' => $verified,
|
|
'roles_mask' => 0,
|
|
'status' => $status,
|
|
'registered' => \time()
|
|
]
|
|
);
|
|
$userId = $db->getlastInsertId();
|
|
|
|
$db->commit();
|
|
} catch (Exception $e) {
|
|
$db->rollBack();
|
|
$this->container->get('flash')->addMessage('error', 'Database failure: ' . $e->getMessage());
|
|
return $response->withHeader('Location', '/user/create')->withStatus(302);
|
|
}
|
|
|
|
$db->exec('UPDATE users SET password_last_updated = NOW() WHERE id = ?', [$userId]);
|
|
$this->container->get('flash')->addMessage('success', 'User ' . $email . ' has been created successfully');
|
|
return $response->withHeader('Location', '/users')->withStatus(302);
|
|
}
|
|
}
|
|
}
|
|
|
|
$db = $this->container->get('db');
|
|
$registrars = $db->select("SELECT id, clid, name FROM registrar");
|
|
if ($_SESSION["auth_roles"] != 0) {
|
|
$registrar = true;
|
|
} else {
|
|
$registrar = null;
|
|
}
|
|
|
|
// Default view for GET requests or if POST data is not set
|
|
return view($response,'admin/users/createUser.twig', [
|
|
'registrars' => $registrars,
|
|
'registrar' => $registrar,
|
|
]);
|
|
}
|
|
|
|
public function updateUser(Request $request, Response $response, $args)
|
|
{
|
|
$db = $this->container->get('db');
|
|
// Get the current URI
|
|
$uri = $request->getUri()->getPath();
|
|
$registrars = $db->select("SELECT id, clid, name FROM registrar");
|
|
|
|
if ($args) {
|
|
$args = trim($args);
|
|
|
|
if (!preg_match('/^[a-z0-9_-]+$/', $args)) {
|
|
$this->container->get('flash')->addMessage('error', 'Invalid user name');
|
|
return $response->withHeader('Location', '/users')->withStatus(302);
|
|
}
|
|
|
|
$user = $db->selectRow('SELECT id,email,username,status,verified,roles_mask,registered,last_login FROM users WHERE username = ?',
|
|
[ $args ]);
|
|
$user_asso = $db->selectValue('SELECT registrar_id FROM registrar_users WHERE user_id = ?',
|
|
[ $user['id'] ]);
|
|
$registrar_name = $db->selectValue('SELECT name FROM registrar WHERE id = ?',
|
|
[ $user_asso ]);
|
|
|
|
if ($user) {
|
|
// Check if the user is not an admin (assuming role 0 is admin)
|
|
if ($_SESSION["auth_roles"] != 0) {
|
|
return $response->withHeader('Location', '/dashboard')->withStatus(302);
|
|
}
|
|
|
|
$_SESSION['user_to_update'] = [$args];
|
|
|
|
$roles_new = [
|
|
'4' => ($user['roles_mask'] & 4) ? true : false, // Registrar
|
|
'8' => ($user['roles_mask'] & 8) ? true : false, // Accountant
|
|
'16' => ($user['roles_mask'] & 16) ? true : false, // Support
|
|
'32' => ($user['roles_mask'] & 32) ? true : false, // Auditor
|
|
'64' => ($user['roles_mask'] & 64) ? true : false, // Sales
|
|
];
|
|
|
|
return view($response,'admin/users/updateUser.twig', [
|
|
'user' => $user,
|
|
'currentUri' => $uri,
|
|
'registrars' => $registrars,
|
|
'user_asso' => $user_asso,
|
|
'registrar_name' => $registrar_name,
|
|
'roles_new' => $roles_new
|
|
]);
|
|
} else {
|
|
// User does not exist, redirect to the users view
|
|
return $response->withHeader('Location', '/users')->withStatus(302);
|
|
}
|
|
} else {
|
|
// Redirect to the users view
|
|
return $response->withHeader('Location', '/users')->withStatus(302);
|
|
}
|
|
}
|
|
|
|
public function updateUserProcess(Request $request, Response $response)
|
|
{
|
|
if ($_SESSION["auth_roles"] != 0) {
|
|
return $response->withHeader('Location', '/dashboard')->withStatus(302);
|
|
}
|
|
|
|
if ($request->getMethod() === 'POST') {
|
|
// Retrieve POST data
|
|
$data = $request->getParsedBody();
|
|
$db = $this->container->get('db');
|
|
|
|
$email = $data['email'] ?? null;
|
|
$old_username = $_SESSION['user_to_update'][0];
|
|
$username = $data['username'] ?? null;
|
|
$password = $data['password'] ?? null;
|
|
$password_confirmation = $data['password_confirmation'] ?? null;
|
|
$status = $data['status'] ?? null;
|
|
$verified = $data['verified'] ?? null;
|
|
$roles_mask = isset($data['roles_mask']) ? (int)$data['roles_mask'] : null;
|
|
|
|
$allowedRoles = [0, 2, 4, 8, 16, 32, 64];
|
|
$allowedRolesMask = array_sum($allowedRoles); // 124 (sum of allowed roles)
|
|
|
|
// Define validation rules
|
|
$validators = [
|
|
'email' => v::email()->notEmpty()->setName('Email'),
|
|
'username' => v::regex('/^[a-zA-Z0-9_-]+$/')->length(3, 20)->setName('Username'),
|
|
'status' => v::in(['0', '1', '2', '3', '4', '5'])->setName('Status'),
|
|
'verified' => v::in(['0', '1'])->setName('Verified'), // Ensure verified is checked as 0 or 1
|
|
];
|
|
|
|
// Add custom validation for roles_mask
|
|
$validators['roles_mask'] = v::oneOf(
|
|
v::intVal()->callback(function ($value) use ($allowedRolesMask) {
|
|
return ($value & ~$allowedRolesMask) === 0; // Ensure only allowed roles are included
|
|
}),
|
|
v::nullType() // Allow null as a valid value
|
|
)->setName('Roles Mask');
|
|
|
|
// Add password validation only if provided
|
|
if (!empty($password)) {
|
|
$validators['password'] = v::stringType()->notEmpty()->length(6, 255)->setName('Password');
|
|
|
|
// Add password confirmation check only if both fields are provided
|
|
if (!empty($password_confirmation)) {
|
|
$validators['password_confirmation'] = v::equals($password)->setName('Password Confirmation');
|
|
}
|
|
}
|
|
|
|
// Validate data
|
|
$errors = [];
|
|
foreach ($validators as $field => $validator) {
|
|
try {
|
|
$validator->assert($data[$field] ?? null);
|
|
} catch (\Respect\Validation\Exceptions\ValidationException $exception) {
|
|
$errors[$field] = $exception->getMessages(); // Collect all error messages
|
|
}
|
|
}
|
|
|
|
// If errors exist, return with errors
|
|
if (!empty($errors)) {
|
|
// Flatten the errors array into a string
|
|
$errorMessages = [];
|
|
foreach ($errors as $field => $fieldErrors) {
|
|
$fieldMessages = implode(', ', $fieldErrors); // Concatenate messages for the field
|
|
$errorMessages[] = ucfirst($field) . ': ' . $fieldMessages; // Prefix with field name
|
|
}
|
|
$errorString = implode('; ', $errorMessages); // Join all fields' errors
|
|
|
|
// Add the flattened error string as a flash message
|
|
$this->container->get('flash')->addMessage('error', 'Error: ' . $errorString);
|
|
return $response->withHeader('Location', '/user/update/'.$old_username)->withStatus(302);
|
|
}
|
|
|
|
if (!empty($password)) {
|
|
if (!checkPasswordComplexity($password)) {
|
|
$this->container->get('flash')->addMessage('error', 'Password too weak. Use a stronger password');
|
|
return $response->withHeader('Location', '/user/update/'.$old_username)->withStatus(302);
|
|
}
|
|
}
|
|
|
|
// Check if username already exists (excluding the current user)
|
|
if ($username && $username !== $old_username) {
|
|
$existingUsername = $db->selectValue('SELECT COUNT(*) FROM users WHERE username = ? AND username != ?', [$username, $old_username]);
|
|
if ($existingUsername > 0) {
|
|
$errors[] = 'Username already exists';
|
|
}
|
|
}
|
|
|
|
// Check if email already exists (excluding the current user)
|
|
if ($email) {
|
|
$existingEmail = $db->selectValue(
|
|
'SELECT COUNT(*) FROM users WHERE email = ? AND username != ?',
|
|
[$email, $old_username]
|
|
);
|
|
if ($existingEmail > 0) {
|
|
$errors[] = 'Email already exists';
|
|
}
|
|
}
|
|
|
|
// Fetch current roles_mask from the database
|
|
$currentRolesMask = $db->selectValue(
|
|
'SELECT roles_mask FROM users WHERE username = ?',
|
|
[$old_username]
|
|
);
|
|
|
|
if ($currentRolesMask !== null) {
|
|
// Prevent lowering privileges by setting roles_mask to 0 unless it was already 0
|
|
if ($roles_mask == 0 && $currentRolesMask != 0) {
|
|
$errors[] = 'You cannot elevate role to admin unless the user was already admin';
|
|
}
|
|
|
|
// Prevent elevating privileges to 4 unless the user was already 4
|
|
if ($roles_mask == 4 && $currentRolesMask != 4) {
|
|
$errors[] = 'You cannot elevate role to registrar unless the user was already registrar';
|
|
}
|
|
}
|
|
|
|
// Handle errors
|
|
if (!empty($errors)) {
|
|
foreach ($errors as $error) {
|
|
$this->container->get('flash')->addMessage('error', $error);
|
|
}
|
|
return $response->withHeader('Location', '/user/update/' . $old_username)->withStatus(302);
|
|
}
|
|
|
|
if (empty($email)) {
|
|
$this->container->get('flash')->addMessage('error', 'No email specified for update');
|
|
return $response->withHeader('Location', '/user/update/'.$old_username)->withStatus(302);
|
|
}
|
|
|
|
if ($roles_mask === null) {
|
|
$this->container->get('flash')->addMessage('error', 'No roles assigned. Please assign at least one role');
|
|
return $response->withHeader('Location', '/user/update/' . $old_username)->withStatus(302);
|
|
}
|
|
|
|
$db->beginTransaction();
|
|
|
|
try {
|
|
$currentDateTime = new \DateTime();
|
|
$update = $currentDateTime->format('Y-m-d H:i:s.v');
|
|
|
|
// Prepare the data to update
|
|
$updateData = [
|
|
'email' => $email,
|
|
'username' => $username,
|
|
'verified' => $verified,
|
|
'status' => $status,
|
|
'roles_mask' => $roles_mask,
|
|
];
|
|
|
|
if (!empty($password)) {
|
|
$password_hashed = password_hash($password, PASSWORD_ARGON2ID, ['memory_cost' => 1024 * 128, 'time_cost' => 6, 'threads' => 4]);
|
|
$updateData['password'] = $password_hashed;
|
|
}
|
|
|
|
$db->update(
|
|
'users',
|
|
$updateData,
|
|
[
|
|
'username' => $old_username
|
|
]
|
|
);
|
|
|
|
$db->commit();
|
|
} catch (Exception $e) {
|
|
$db->rollBack();
|
|
$this->container->get('flash')->addMessage('error', 'Database failure during update: ' . $e->getMessage());
|
|
return $response->withHeader('Location', '/user/update/'.$old_username)->withStatus(302);
|
|
}
|
|
|
|
$userId = $db->selectValue('SELECT id from users WHERE username = ?', [ $username ]);
|
|
unset($_SESSION['user_to_update']);
|
|
$db->exec('UPDATE users SET password_last_updated = NOW() WHERE id = ?', [$userId]);
|
|
$this->container->get('flash')->addMessage('success', 'User ' . $username . ' has been updated successfully on ' . $update);
|
|
return $response->withHeader('Location', '/user/update/'.$username)->withStatus(302);
|
|
}
|
|
}
|
|
|
|
} |