GeoNetAgent, LDAPWeb, server-audit, server-connection
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

<?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;
}
}