Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 92
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
CreateUserEndpoint
0.00% covered (danger)
0.00%
0 / 92
0.00% covered (danger)
0.00%
0 / 2
756
0.00% covered (danger)
0.00%
0 / 1
 configure
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 handle
0.00% covered (danger)
0.00%
0 / 89
0.00% covered (danger)
0.00%
0 / 1
702
1<?php
2
3namespace Olz\Users\Endpoints;
4
5use Olz\Api\OlzCreateEntityTypedEndpoint;
6use Olz\Entity\AuthRequest;
7use Olz\Entity\Users\User;
8use PhpTypeScriptApi\Fields\ValidationError;
9use PhpTypeScriptApi\HttpError;
10
11/**
12 * @phpstan-import-type OlzUserId from UserEndpointTrait
13 * @phpstan-import-type OlzUserData from UserEndpointTrait
14 *
15 * @extends OlzCreateEntityTypedEndpoint<OlzUserId, OlzUserData, array{
16 *   captchaToken?: ?non-empty-string,
17 * }, array{
18 *   status: 'OK'|'OK_NO_EMAIL_VERIFICATION'|'DENIED'|'ERROR',
19 * }>
20 */
21class CreateUserEndpoint extends OlzCreateEntityTypedEndpoint {
22    use UserEndpointTrait;
23
24    public function configure(): void {
25        parent::configure();
26        $this->configureUserEndpointTrait();
27        $this->phpStanUtils->registerTypeImport(UserEndpointTrait::class);
28    }
29
30    protected function handle(mixed $input): mixed {
31        $current_user = $this->authUtils()->getCurrentUser();
32        $token = $input['custom']['captchaToken'] ?? null;
33        if (!$current_user && !$this->captchaUtils()->validateToken($token)) {
34            return ['custom' => ['status' => 'DENIED'], 'id' => null];
35        }
36
37        $parent_user_id = $input['data']['parentUserId'] ?? null;
38        if ($parent_user_id !== null) {
39            if (!$current_user) {
40                throw new HttpError(403, "Kein Zugriff!");
41            }
42            if ($parent_user_id !== $current_user->getId()) {
43                // Create child of someone else
44                if (!$this->authUtils()->hasPermission('users')) {
45                    throw new HttpError(403, "Kein Zugriff!");
46                }
47            }
48        }
49
50        $first_name = $input['data']['firstName'];
51        $last_name = $input['data']['lastName'];
52        $username = $input['data']['username'];
53        $email = $input['data']['email'] ?? null;
54        $password = $input['data']['password'] ?? null;
55        $this->log()->info("New sign-up (using password): {$first_name} {$last_name} ({$username}@) <{$email}> (Parent: {$parent_user_id})");
56        if (!$parent_user_id && !$email) {
57            throw new ValidationError(['email' => ["Feld darf nicht leer sein."]]);
58        }
59        if (!$parent_user_id && !$password) {
60            throw new ValidationError(['password' => ["Feld darf nicht leer sein."]]);
61        }
62        if (!$this->authUtils()->isUsernameAllowed($username)) {
63            throw new ValidationError(['username' => ["Der Benutzername darf nur Buchstaben, Zahlen, und die Zeichen -_. enthalten."]]);
64        }
65        if (!$parent_user_id && !$this->authUtils()->isPasswordAllowed($password)) {
66            throw new ValidationError(['password' => ["Das Passwort muss mindestens 8 Zeichen lang sein."]]);
67        }
68        if ($email && preg_match('/@olzimmerberg\.ch$/i', $email)) {
69            throw new ValidationError(['email' => ["Bitte keine @olzimmerberg.ch E-Mail verwenden."]]);
70        }
71        $ip_address = $this->server()['REMOTE_ADDR'];
72        $auth_request_repo = $this->entityManager()->getRepository(AuthRequest::class);
73        $user_repo = $this->entityManager()->getRepository(User::class);
74
75        $same_username_user = $user_repo->findOneBy(['username' => $username]);
76        $same_email_user = $user_repo->findOneBy(['email' => $email]);
77        // TODO: Test users with old username, roles
78        if ($username && $same_username_user) {
79            if ($same_username_user->getPasswordHash()) {
80                throw new ValidationError(['username' => ["Es existiert bereits eine Person mit diesem Benutzernamen. Wolltest du gar kein Konto erstellen, sondern dich nur einloggen?"]]);
81            }
82            // If it's an existing user WITHOUT password, we just update that existing user!
83            $entity = $same_username_user;
84            $this->entityUtils()->updateOlzEntity($entity, ['onOff' => true]);
85        } elseif ($email && $same_email_user) {
86            if ($same_email_user->getPasswordHash()) {
87                throw new ValidationError(['email' => ["Es existiert bereits eine Person mit dieser E-Mail Adresse. Wolltest du gar kein Konto erstellen, sondern dich nur einloggen?"]]);
88            }
89            // If it's an existing user WITHOUT password, we just update that existing user!
90            $entity = $same_email_user;
91            $this->entityUtils()->updateOlzEntity($entity, ['onOff' => true]);
92        } else {
93            $entity = new User();
94            $this->entityUtils()->createOlzEntity($entity, ['onOff' => true]);
95        }
96
97        $entity->setOldUsername(null);
98        $this->updateEntityWithData($entity, $input['data']);
99        $entity->setEmailIsVerified(false);
100        $entity->setEmailVerificationToken(null);
101
102        $password_hash = $password ? $this->authUtils()->hashPassword($password) : null;
103
104        $entity->setPasswordHash($password_hash);
105        $entity->setParentUserId($parent_user_id);
106        $entity->setPermissions('');
107        $entity->setRoot(null);
108        $entity->setMemberType(null);
109        $entity->setMemberLastPaid(null);
110        $entity->setWantsPostalMail(false);
111        $entity->setPostalTitle(null);
112        $entity->setPostalName(null);
113        $entity->setJoinedOn(null);
114        $entity->setJoinedReason(null);
115        $entity->setLeftOn(null);
116        $entity->setLeftReason(null);
117        $entity->setNotes('');
118        $entity->setLastLoginAt(null);
119
120        $this->entityManager()->persist($entity);
121        $this->entityManager()->flush();
122        $this->persistUploads($entity, $input['data']);
123
124        if (!$parent_user_id) {
125            $this->session()->resetConfigure(['timeout' => 3600]);
126
127            $root = $entity->getRoot() !== '' ? $entity->getRoot() : './';
128            $this->session()->set('auth', $entity->getPermissions());
129            $this->session()->set('root', $root);
130            $this->session()->set('user', $entity->getUsername());
131            $this->session()->set('user_id', "{$entity->getId()}");
132            $this->session()->set('auth_user', $entity->getUsername());
133            $this->session()->set('auth_user_id', "{$entity->getId()}");
134            $auth_request_repo->addAuthRequest($ip_address, 'AUTHENTICATED_PASSWORD', $entity->getUsername());
135
136            $this->emailUtils()->setLogger($this->log());
137            try {
138                $this->emailUtils()->sendEmailVerificationEmail($entity);
139            } catch (\Throwable $th) {
140                return [
141                    'custom' => ['status' => 'OK_NO_EMAIL_VERIFICATION'],
142                    'id' => $entity->getId() ?? 0,
143                ];
144            }
145            $this->entityManager()->flush();
146        }
147
148        return [
149            'custom' => ['status' => 'OK'],
150            'id' => $entity->getId() ?? 0,
151        ];
152    }
153}