You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
10 KiB
273 lines
10 KiB
<?php |
|
|
|
namespace App\Http\Controllers; |
|
|
|
use App\Services\AuditLogService; |
|
use App\Services\LdapGroupService; |
|
use App\Services\LdapMetadataService; |
|
use App\Services\LdapPasswordResetService; |
|
use App\Services\LdapUserService; |
|
use Illuminate\Http\RedirectResponse; |
|
use Illuminate\Http\Request; |
|
use Illuminate\View\View; |
|
use InvalidArgumentException; |
|
use RuntimeException; |
|
use Throwable; |
|
|
|
class LdapUserController extends Controller |
|
{ |
|
public function __construct( |
|
private readonly LdapUserService $ldapService, |
|
private readonly LdapGroupService $groupService, |
|
private readonly LdapPasswordResetService $passwordReset, |
|
private readonly LdapMetadataService $metadata, |
|
private readonly AuditLogService $audit |
|
) { |
|
} |
|
|
|
public function index(Request $request): View |
|
{ |
|
$sort = $this->sortParam($request, ['username', 'display_name', 'email', 'phone', 'enabled', 'last_logon', 'groups', 'groups_count'], 'username'); |
|
$dir = $request->string('dir', 'asc')->toString() === 'desc' ? 'desc' : 'asc'; |
|
$search = $request->string('q')->trim()->toString(); |
|
$groupFilter = $request->string('group')->trim()->toString(); |
|
|
|
$result = $this->ldapService->listUsers($sort, $dir, $search, $groupFilter); |
|
$users = $this->metadata->enrichUsers($result['rows']); |
|
|
|
if ($search !== '') { |
|
$ldapUsernames = array_column($users, 'username'); |
|
|
|
foreach ($this->metadata->findUsernamesByMetaSearch($search) as $username) { |
|
if (in_array($username, $ldapUsernames, true)) { |
|
continue; |
|
} |
|
|
|
try { |
|
$user = $this->ldapService->getUser($username); |
|
$users[] = array_merge($user, $this->metadata->getUserMeta($username)); |
|
} catch (InvalidArgumentException) { |
|
} |
|
} |
|
|
|
usort($users, fn ($a, $b) => strcmp($a['username'], $b['username'])); |
|
} |
|
|
|
if (in_array($sort, ['phone'], true)) { |
|
usort($users, function (array $a, array $b) use ($sort, $dir) { |
|
$cmp = ($a[$sort] ?? '') <=> ($b[$sort] ?? ''); |
|
|
|
return $dir === 'desc' ? -$cmp : $cmp; |
|
}); |
|
} |
|
|
|
return view('ldap-users.index', [ |
|
'users' => $users, |
|
'meta' => array_merge($result['meta'], ['total' => count($users)]), |
|
'sort' => $sort, |
|
'dir' => $dir, |
|
'search' => $search, |
|
'groupFilter' => $groupFilter, |
|
'error' => $result['error'] ?? null, |
|
]); |
|
} |
|
|
|
public function show(string $username): View|RedirectResponse |
|
{ |
|
try { |
|
$user = $this->ldapService->getUser($username); |
|
$meta = $this->metadata->getUserMeta($username); |
|
$user = array_merge($user, $meta); |
|
$activity = $this->audit->listForUser($username, 50); |
|
|
|
return view('ldap-users.show', [ |
|
'user' => $user, |
|
'activityLogs' => $activity['rows'], |
|
'activityError' => $activity['error'], |
|
]); |
|
} catch (InvalidArgumentException $e) { |
|
return redirect()->route('ldap-users.index')->with('error', $e->getMessage()); |
|
} |
|
} |
|
|
|
public function edit(string $username): View|RedirectResponse |
|
{ |
|
try { |
|
$user = $this->ldapService->getUser($username); |
|
$user = array_merge($user, $this->metadata->getUserMeta($username)); |
|
$groupsResult = $this->groupService->listGroups('name', 'asc'); |
|
$allGroups = $groupsResult['rows'] ?? []; |
|
$groupsByName = []; |
|
|
|
foreach ($allGroups as $group) { |
|
$groupsByName[$group['name']] = $group; |
|
} |
|
|
|
$currentGroups = []; |
|
|
|
foreach ($user['groups'] as $groupName) { |
|
$currentGroups[] = $groupsByName[$groupName] ?? [ |
|
'name' => $groupName, |
|
'route_key' => rawurlencode($groupName), |
|
'protected' => true, |
|
]; |
|
} |
|
|
|
$availableGroups = array_values(array_filter( |
|
$allGroups, |
|
fn (array $group) => ! $group['protected'] |
|
&& ! in_array($group['name'], $user['groups'], true) |
|
)); |
|
|
|
return view('ldap-users.edit', [ |
|
'user' => $user, |
|
'currentGroups' => $currentGroups, |
|
'availableGroups' => $availableGroups, |
|
]); |
|
} catch (InvalidArgumentException $e) { |
|
return redirect()->route('ldap-users.index')->with('error', $e->getMessage()); |
|
} |
|
} |
|
|
|
public function update(Request $request, string $username): RedirectResponse |
|
{ |
|
$data = $request->validate([ |
|
'display_name' => ['required', 'string', 'max:255'], |
|
'email' => ['nullable', 'email', 'max:255'], |
|
'other_email' => ['nullable', 'email', 'max:255'], |
|
'phone' => ['nullable', 'string', 'max:32'], |
|
'admin_note' => ['nullable', 'string', 'max:5000'], |
|
]); |
|
|
|
try { |
|
$this->ldapService->updateProfile($username, $data['display_name'], $data['email'] ?? null); |
|
$this->metadata->saveUserMeta($username, [ |
|
'other_email' => $data['other_email'] ?? null, |
|
'phone' => $data['phone'] ?? null, |
|
'admin_note' => $data['admin_note'] ?? null, |
|
]); |
|
$request->attributes->set('audit_metadata', [ |
|
'username' => $username, |
|
'display_name' => $data['display_name'], |
|
]); |
|
|
|
return redirect()->route('ldap-users.show', $username)->with('success', "User [{$username}] updated."); |
|
} catch (InvalidArgumentException|RuntimeException $e) { |
|
return back()->withInput()->with('error', $e->getMessage()); |
|
} catch (Throwable) { |
|
return back()->withInput()->with('error', 'Failed to update user.'); |
|
} |
|
} |
|
|
|
public function toggleEnabled(Request $request, string $username): RedirectResponse |
|
{ |
|
$request->validate(['confirm' => ['required', 'accepted']]); |
|
|
|
try { |
|
$user = $this->ldapService->getUser($username); |
|
$this->ldapService->setEnabled($username, ! $user['enabled']); |
|
$state = $user['enabled'] ? 'disabled' : 'enabled'; |
|
$request->attributes->set('audit_metadata', [ |
|
'username' => $username, |
|
'enabled' => ! $user['enabled'], |
|
]); |
|
|
|
return redirect()->route('ldap-users.index')->with('success', "User [{$username}] is now {$state}."); |
|
} catch (InvalidArgumentException|RuntimeException $e) { |
|
return redirect()->route('ldap-users.index')->with('error', $e->getMessage()); |
|
} catch (Throwable) { |
|
return redirect()->route('ldap-users.index')->with('error', 'Failed to update account status.'); |
|
} |
|
} |
|
|
|
public function showPasswordForm(string $username): View|RedirectResponse |
|
{ |
|
try { |
|
$user = $this->ldapService->getUser($username); |
|
|
|
if ($user['protected']) { |
|
return redirect()->route('ldap-users.index')->with('error', 'This account is protected.'); |
|
} |
|
|
|
return view('ldap-users.password', compact('user')); |
|
} catch (InvalidArgumentException $e) { |
|
return redirect()->route('ldap-users.index')->with('error', $e->getMessage()); |
|
} |
|
} |
|
|
|
public function addToGroup(Request $request, string $username): RedirectResponse |
|
{ |
|
$data = $request->validate([ |
|
'group' => ['required', 'string', 'max:255'], |
|
]); |
|
|
|
try { |
|
$this->groupService->addMember($data['group'], $username); |
|
$request->attributes->set('audit_metadata', [ |
|
'username' => $username, |
|
'group' => $data['group'], |
|
]); |
|
|
|
return redirect() |
|
->route('ldap-users.edit', $username) |
|
->with('success', "User [{$username}] added to group."); |
|
} catch (InvalidArgumentException|RuntimeException $e) { |
|
return back()->with('error', $e->getMessage()); |
|
} catch (Throwable) { |
|
return back()->with('error', 'Failed to add user to group.'); |
|
} |
|
} |
|
|
|
public function removeFromGroup(Request $request, string $username, string $group): RedirectResponse |
|
{ |
|
$request->validate(['confirm' => ['required', 'accepted']]); |
|
|
|
try { |
|
$this->groupService->removeMember($group, $username); |
|
$request->attributes->set('audit_metadata', [ |
|
'username' => $username, |
|
'group' => $group, |
|
]); |
|
|
|
return redirect() |
|
->route('ldap-users.edit', $username) |
|
->with('success', "User [{$username}] removed from group."); |
|
} catch (InvalidArgumentException|RuntimeException $e) { |
|
return back()->with('error', $e->getMessage()); |
|
} catch (Throwable) { |
|
return back()->with('error', 'Failed to remove user from group.'); |
|
} |
|
} |
|
|
|
public function resetPassword(Request $request, string $username): RedirectResponse |
|
{ |
|
$data = $request->validate([ |
|
'password' => ['required', 'string', 'min:8', 'max:128', 'confirmed'], |
|
'confirm_action' => ['required', 'accepted'], |
|
'confirm_username' => ['required', 'string'], |
|
]); |
|
|
|
if (mb_strtolower($data['confirm_username']) !== mb_strtolower($username)) { |
|
return back()->withInput()->with('error', 'Username confirmation does not match.'); |
|
} |
|
|
|
try { |
|
$this->ldapService->resetPassword($username, $data['password']); |
|
$this->passwordReset->notifyPasswordChanged($username, session('ldap_username')); |
|
$request->attributes->set('audit_metadata', ['username' => $username]); |
|
|
|
return redirect()->route('ldap-users.index')->with('success', "Password for [{$username}] has been reset."); |
|
} catch (InvalidArgumentException|RuntimeException $e) { |
|
return back()->withInput()->with('error', $e->getMessage()); |
|
} catch (Throwable) { |
|
return back()->withInput()->with('error', 'Password reset failed.'); |
|
} |
|
} |
|
|
|
private function sortParam(Request $request, array $allowed, string $default): string |
|
{ |
|
$sort = $request->string('sort', $default)->toString(); |
|
|
|
return in_array($sort, $allowed, true) ? $sort : $default; |
|
} |
|
}
|
|
|