First public release. - docs: document the audit log, user offboarding, CSV export and the "require a reason for destructive actions" setting across README, docs/ and CLAUDE/AGENTS.md; note retention for panel_audit - docs: fill the publishing placeholders (repo URL, security contact, issue-template links) and add a semantic-versioning policy - move agent/contributor context into CLAUDE/ (AGENTS.md + a README); root CLAUDE.md is now a thin @-import pointer - add a favicon (shield-lock, matching the login screen) to the login page and the app layout - CHANGELOG: promote [Unreleased] to [1.0.0]; keep the earlier internal build as [0.1.0] Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UfuvfwexztdS7LDpGuKsuK
144 lines
6.9 KiB
PHP
144 lines
6.9 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Services\AuditLogger;
|
|
use App\Services\PanelSettingsService;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
|
|
class ConfigController extends Controller
|
|
{
|
|
public function __construct(protected AuditLogger $audit) {}
|
|
|
|
public function index(PanelSettingsService $settings)
|
|
{
|
|
return view('admin.config.index', [
|
|
'settings' => $settings->all(),
|
|
'attributes' => $settings->attributes(),
|
|
'attributeCategories' => $settings->categories(),
|
|
'attributeTypeLabels' => PanelSettingsService::ATTRIBUTE_TYPE_LABELS,
|
|
'editableAttributeTypes' => PanelSettingsService::EDITABLE_ATTRIBUTE_TYPES,
|
|
'ldapDefaults' => [
|
|
'user_ou' => config('ldap.defaults.user_ou'),
|
|
'computer_ou' => config('ldap.defaults.computer_ou'),
|
|
'groups_ou' => config('ldap.defaults.groups_ou'),
|
|
'domain_label' => config('ldap.defaults.domain_label'),
|
|
],
|
|
'ldapConnection' => [
|
|
'hosts' => config('ldap.connections.default.hosts'),
|
|
'base_dn' => config('ldap.connections.default.base_dn'),
|
|
'encrypted' => (bool) config('ldap.connections.default.use_tls')
|
|
|| (bool) config('ldap.connections.default.use_starttls'),
|
|
'username' => config('ldap.connections.default.username'),
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function update(Request $request, PanelSettingsService $settings): RedirectResponse
|
|
{
|
|
$data = $request->validate([
|
|
'reset_password.must_change_password' => ['nullable', 'boolean'],
|
|
'reset_password.unlock_account' => ['nullable', 'boolean'],
|
|
'password_policy.max_age_days' => ['required', 'integer', 'min:0', 'max:3650'],
|
|
'password_policy.generator_length' => ['nullable', 'integer', 'min:8', 'max:128'],
|
|
'password_policy.generator_symbols' => ['nullable', 'boolean'],
|
|
'encryption.key' => ['nullable', 'string', 'max:2048'],
|
|
'audit.require_reason' => ['nullable', 'boolean'],
|
|
]);
|
|
|
|
$current = $settings->all();
|
|
|
|
$current['reset_password']['must_change_password'] = (bool) ($data['reset_password']['must_change_password'] ?? false);
|
|
$current['reset_password']['unlock_account'] = (bool) ($data['reset_password']['unlock_account'] ?? false);
|
|
$current['password_policy']['max_age_days'] = (int) $data['password_policy']['max_age_days'];
|
|
$current['password_policy']['generator_length'] = (int) ($data['password_policy']['generator_length'] ?? 16);
|
|
$current['password_policy']['generator_symbols'] = (bool) ($data['password_policy']['generator_symbols'] ?? false);
|
|
$current['encryption']['key'] = trim((string) ($data['encryption']['key'] ?? ''));
|
|
$current['audit']['require_reason'] = (bool) ($data['audit']['require_reason'] ?? false);
|
|
|
|
$settings->save($current);
|
|
|
|
$this->audit->log('config.settings', [
|
|
'context' => [
|
|
'max_age_days' => $current['password_policy']['max_age_days'],
|
|
'require_reason' => $current['audit']['require_reason'],
|
|
'encryption_key_set' => $current['encryption']['key'] !== '',
|
|
],
|
|
]);
|
|
|
|
return back()->with('status', __('config.flash.saved'));
|
|
}
|
|
|
|
/**
|
|
* Zapisuje graficznie skonfigurowaną listę atrybutów AD (zakładka "Atrybuty"):
|
|
* dodanie / edycja / usunięcie, widoczność jako kolumna listy użytkowników
|
|
* oraz przypisanie do kategorii w oknie podglądu użytkownika.
|
|
*/
|
|
public function updateAttributes(Request $request, PanelSettingsService $settings): RedirectResponse
|
|
{
|
|
$validCategories = array_keys($settings->categories());
|
|
|
|
$data = $request->validate([
|
|
'attributes' => ['array'],
|
|
'attributes.*.key' => ['nullable', 'string', 'max:64', 'regex:/^[A-Za-z0-9_-]*$/'],
|
|
'attributes.*.label' => ['nullable', 'string', 'max:128'],
|
|
'attributes.*.ldap' => ['nullable', 'string', 'max:128', 'regex:/^[A-Za-z0-9-]*$/'],
|
|
'attributes.*.category' => ['nullable', 'string'],
|
|
'attributes.*.type' => ['nullable', 'string', 'in:'.implode(',', PanelSettingsService::ATTRIBUTE_TYPES)],
|
|
'attributes.*.column' => ['nullable', 'boolean'],
|
|
'attributes.*.regex' => ['nullable', 'string', 'max:191', function (string $attribute, mixed $value, \Closure $fail) {
|
|
if (is_string($value) && $value !== '' && @preg_match($value, '') === false) {
|
|
$fail(__('config.validation.regex_invalid'));
|
|
}
|
|
}],
|
|
'attributes.*.required' => ['nullable', 'boolean'],
|
|
'attributes.*.help' => ['nullable', 'string', 'max:255'],
|
|
], [
|
|
'attributes.*.key.regex' => __('config.validation.key_regex'),
|
|
'attributes.*.ldap.regex' => __('config.validation.ldap_regex'),
|
|
]);
|
|
|
|
$computedKeys = PanelSettingsService::COMPUTED_ATTRIBUTE_KEYS;
|
|
|
|
$rows = collect($data['attributes'] ?? [])
|
|
->map(function (array $row) use ($validCategories, $computedKeys) {
|
|
$key = trim((string) ($row['key'] ?? ''));
|
|
$category = $row['category'] ?? 'general';
|
|
$isComputed = in_array($key, $computedKeys, true);
|
|
|
|
return [
|
|
'key' => $key,
|
|
'label' => trim((string) ($row['label'] ?? '')),
|
|
// Kolumny wyliczane nie mają atrybutu AD - podstawiamy klucz,
|
|
// żeby przeszły przez filtr; PanelSettingsService i tak to wymusi.
|
|
'ldap' => $isComputed ? $key : trim((string) ($row['ldap'] ?? '')),
|
|
'category' => in_array($category, $validCategories, true) ? $category : 'general',
|
|
// Typ (tekst / liczba / data / tekst szyfrowany ...) - dla
|
|
// pozycji wbudowanych i tak wymusi go PanelSettingsService.
|
|
'type' => (string) ($row['type'] ?? 'text'),
|
|
// Reguły walidacji pola profilu - PanelSettingsService czyści
|
|
// je dla pozycji nieedytowalnych.
|
|
'regex' => trim((string) ($row['regex'] ?? '')),
|
|
'required' => (bool) ($row['required'] ?? false),
|
|
'help' => trim((string) ($row['help'] ?? '')),
|
|
'column' => (bool) ($row['column'] ?? false),
|
|
];
|
|
})
|
|
->filter(fn (array $row) => $row['key'] !== '' && $row['ldap'] !== '')
|
|
->values()
|
|
->all();
|
|
|
|
$settings->saveAttributes($rows);
|
|
|
|
$this->audit->log('config.attributes', [
|
|
'context' => ['count' => count($rows)],
|
|
]);
|
|
|
|
return back()
|
|
->with('status', __('config.flash.attributes_saved'))
|
|
->withFragment('attributes');
|
|
}
|
|
}
|