asdasd
All checks were successful
Deploy / deploy-staging (push) Successful in 1m3s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-06-15 02:05:36 +02:00
parent e9b86eb7a3
commit 9325b93404
18 changed files with 1102 additions and 268 deletions

View File

@@ -10,6 +10,8 @@ final class RegistrationService
private RegistrationLdifBuilder $ldifBuilder;
private RegistrationSecretBox $secretBox;
private LdapProvisioner $ldapProvisioner;
private RegistrationIdentityAllocator $identityAllocator;
private PasswordResetService $passwordResetService;
/**
* @param array<string, mixed> $config
@@ -23,6 +25,9 @@ final class RegistrationService
$this->ldifBuilder = new RegistrationLdifBuilder($this->config);
$this->secretBox = new RegistrationSecretBox((string) ($this->config['password_crypto_key'] ?? ''));
$this->ldapProvisioner = new LdapProvisioner($this->config);
$counterPath = $this->resolveProjectPath((string) ($this->config['identity_counter_path'] ?? 'data/registration-identity-counter.json'));
$this->identityAllocator = new RegistrationIdentityAllocator($counterPath, $this->config);
$this->passwordResetService = new PasswordResetService($this->projectRoot, $this->config);
}
public function isEnabled(): bool
@@ -100,6 +105,10 @@ final class RegistrationService
$errors[] = 'Für diesen Benutzernamen oder diese E-Mail existiert bereits eine offene oder freigegebene Registrierung.';
}
if ($this->ldapProvisioner->usernameExists($username)) {
$errors[] = 'Dieser Benutzername ist bereits im LDAP vergeben.';
}
if ($errors !== []) {
return [
'success' => false,
@@ -107,9 +116,26 @@ final class RegistrationService
];
}
$identities = $this->identityAllocator->allocate();
$pendingGroup = trim((string) ($this->config['ldap']['pending_group_dn'] ?? ''));
$provisioning = [
'uid_number' => $identities['uid_number'],
'gid_number' => $identities['gid_number'],
'home_directory' => rtrim((string) ($this->config['ldap']['home_directory_prefix'] ?? '/home'), '/') . '/' . $username,
'login_shell' => trim((string) ($this->config['ldap']['default_login_shell'] ?? '/bin/sh')),
'member_of_list' => $pendingGroup,
'samba_sid' => $identities['samba_sid'],
'apple_generateduid' => '',
'display_name' => $displayName,
'cn' => $displayName !== '' ? $displayName : $username,
'gecos' => $displayName,
'admin_note' => '',
'ldap_created_at' => gmdate('c'),
];
$request = [
'id' => bin2hex(random_bytes(8)),
'status' => 'pending',
'status' => 'pending_approval',
'created_at' => gmdate('c'),
'updated_at' => gmdate('c'),
'username' => $username,
@@ -125,8 +151,29 @@ final class RegistrationService
'host' => (string) ($_SERVER['HTTP_HOST'] ?? ''),
],
'review' => null,
'provisioning' => null,
'provisioning' => $provisioning,
];
$passwordSecret = $this->secretBox->encrypt($password);
try {
$ldapResult = $this->ldapProvisioner->createInactiveUser($request, $provisioning, $password);
$request['password_secret'] = null;
$request['provisioning']['ldap_dn'] = $ldapResult['dn'];
$request['provisioning']['group_updates'] = $ldapResult['group_updates'];
$request['provisioning']['status'] = 'ldap_created_inactive';
$request['provisioning']['ldif_path'] = $this->writeLdifDraft($request['id'], $username, $this->ldifBuilder->build($request, $provisioning));
} catch (\Throwable $exception) {
$request['password_secret'] = $passwordSecret;
$request['status'] = 'provisioning_failed';
$request['provisioning']['status'] = 'provisioning_failed';
$request['provisioning']['error'] = $exception->getMessage();
return [
'success' => false,
'errors' => ['LDAP-Provisionierung bei Registrierung fehlgeschlagen: ' . $exception->getMessage()],
'request' => $this->store->create($request),
];
}
return [
'success' => true,
@@ -146,49 +193,45 @@ final class RegistrationService
return ['success' => false, 'errors' => ['Die Registrierung wurde nicht gefunden.']];
}
$uidNumber = trim((string) ($input['uid_number'] ?? ''));
$gidNumber = trim((string) ($input['gid_number'] ?? ''));
$homeDirectory = trim((string) ($input['home_directory'] ?? ''));
$sambaSid = trim((string) ($input['samba_sid'] ?? ''));
$memberOfList = trim((string) ($input['member_of_list'] ?? ''));
$adminNote = trim((string) ($input['admin_note'] ?? ''));
$errors = [];
if ($uidNumber === '' || !ctype_digit($uidNumber)) {
$errors[] = 'uidNumber ist erforderlich und muss numerisch sein.';
}
if ($gidNumber === '' || !ctype_digit($gidNumber)) {
$errors[] = 'gidNumber ist erforderlich und muss numerisch sein.';
}
if ($homeDirectory === '') {
$errors[] = 'homeDirectory ist erforderlich.';
if ($memberOfList === '') {
$errors[] = 'Für die Freischaltung muss mindestens eine Zielgruppe ausgewählt werden.';
}
if ($errors !== []) {
return ['success' => false, 'errors' => $errors];
}
$activate = $this->ldapProvisioner->activateUser((string) ($request['username'] ?? ''), preg_split('/\r\n|\r|\n/', $memberOfList) ?: []);
if (!($activate['success'] ?? false)) {
return ['success' => false, 'errors' => [(string) ($activate['message'] ?? 'Aktivierung fehlgeschlagen.')]];
}
$provisioning = [
'uid_number' => $uidNumber,
'gid_number' => $gidNumber,
'home_directory' => $homeDirectory,
'login_shell' => trim((string) ($input['login_shell'] ?? ($this->config['ldap']['default_login_shell'] ?? '/bin/sh'))),
'uid_number' => (string) ($request['provisioning']['uid_number'] ?? ''),
'gid_number' => (string) ($request['provisioning']['gid_number'] ?? ''),
'home_directory' => (string) ($request['provisioning']['home_directory'] ?? ''),
'login_shell' => (string) ($request['provisioning']['login_shell'] ?? ($this->config['ldap']['default_login_shell'] ?? '/bin/sh')),
'member_of_list' => $memberOfList,
'samba_sid' => $sambaSid,
'apple_generateduid' => trim((string) ($input['apple_generateduid'] ?? '')),
'display_name' => trim((string) ($input['display_name'] ?? (string) ($request['display_name'] ?? ''))),
'cn' => trim((string) ($input['cn'] ?? (string) ($request['display_name'] ?? $request['username'] ?? ''))),
'gecos' => trim((string) ($input['gecos'] ?? (string) ($request['display_name'] ?? ''))),
'samba_sid' => (string) ($request['provisioning']['samba_sid'] ?? ''),
'apple_generateduid' => (string) ($request['provisioning']['apple_generateduid'] ?? ''),
'display_name' => (string) ($request['provisioning']['display_name'] ?? $request['display_name'] ?? ''),
'cn' => (string) ($request['provisioning']['cn'] ?? $request['display_name'] ?? $request['username'] ?? ''),
'gecos' => (string) ($request['provisioning']['gecos'] ?? $request['display_name'] ?? ''),
'admin_note' => $adminNote,
'ldif_generated_at' => gmdate('c'),
'ldap_activated_at' => gmdate('c'),
'ldap_dn' => (string) ($activate['dn'] ?? ($request['provisioning']['ldap_dn'] ?? '')),
'status' => 'approved_active',
];
$request = $this->store->update(
$id,
static function (array $item) use ($provisioning, $reviewer, $adminNote): array {
$item['status'] = 'approved_pending_provisioning';
$item['status'] = 'approved_active';
$item['updated_at'] = gmdate('c');
$item['review'] = [
'reviewed_at' => gmdate('c'),
@@ -206,54 +249,6 @@ final class RegistrationService
return ['success' => false, 'errors' => ['Die Registrierung konnte nicht aktualisiert werden.']];
}
$ldif = $this->ldifBuilder->build($request, $provisioning);
$ldifPath = $this->writeLdifDraft((string) ($request['id'] ?? ''), (string) ($request['username'] ?? ''), $ldif);
try {
$passwordSecret = (string) ($request['password_secret'] ?? '');
if ($passwordSecret === '') {
throw new \RuntimeException('Für diese Registrierung ist kein Passwort mehr hinterlegt.');
}
$plainPassword = $this->secretBox->decrypt($passwordSecret);
$ldapResult = $this->ldapProvisioner->createInactiveUser($request, $provisioning, $plainPassword);
$request = $this->store->update(
$id,
static function (array $item) use ($ldifPath, $ldapResult): array {
$item['status'] = 'ldap_created_inactive';
$item['updated_at'] = gmdate('c');
$item['password_secret'] = null;
$item['provisioning']['ldif_path'] = $ldifPath;
$item['provisioning']['status'] = 'ldap_created_inactive';
$item['provisioning']['ldap_dn'] = $ldapResult['dn'];
$item['provisioning']['group_updates'] = $ldapResult['group_updates'];
return $item;
}
);
} catch (\Throwable $exception) {
$request = $this->store->update(
$id,
static function (array $item) use ($ldifPath, $exception): array {
$item['status'] = 'provisioning_failed';
$item['updated_at'] = gmdate('c');
$item['provisioning']['ldif_path'] = $ldifPath;
$item['provisioning']['status'] = 'provisioning_failed';
$item['provisioning']['error'] = $exception->getMessage();
return $item;
}
);
return [
'success' => false,
'errors' => ['LDAP-Provisionierung fehlgeschlagen: ' . $exception->getMessage()],
'request' => $request,
];
}
return [
'success' => true,
'request' => $request,
@@ -297,6 +292,66 @@ final class RegistrationService
];
}
/**
* @return array{success: bool, message: string}
*/
public function sendPasswordReset(string $username, string $origin): array
{
return $this->passwordResetService->request($username, $origin);
}
/**
* @return array{success: bool, message: string}
*/
public function deactivateUser(string $username): array
{
return $this->ldapProvisioner->deactivateUser($username);
}
/**
* @return array{success: bool, message: string}
*/
public function activateExistingUser(string $username, array $groupDns): array
{
return $this->ldapProvisioner->activateUser($username, $groupDns);
}
/**
* @return array{success: bool, message: string}
*/
public function setUserPassword(string $username, string $password): array
{
return $this->ldapProvisioner->updateUserPassword($username, $password);
}
/**
* @return array<int, array<string, mixed>>
*/
public function pendingApprovals(): array
{
return array_values(array_filter(
$this->all(),
static fn (array $item): bool => in_array((string) ($item['status'] ?? ''), ['pending_approval', 'provisioning_failed'], true)
));
}
/**
* @return array<int, array<string, string>>
*/
public function availableGroups(): array
{
return array_values(array_filter(
array_map(
static fn (array $group): array => [
'dn' => (string) ($group['dn'] ?? ''),
'label' => (string) ($group['label'] ?? ($group['dn'] ?? '')),
],
(array) ($this->config['ldap']['available_groups'] ?? [])
),
static fn (array $group): bool => $group['dn'] !== ''
));
}
private function writeLdifDraft(string $id, string $username, string $content): string
{
$directory = $this->resolveProjectPath((string) ($this->config['ldif_export_dir'] ?? 'data/registration-ldif'));