Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
56 / 56 |
Security | |
100.00% |
1 / 1 |
|
100.00% |
2 / 2 |
11 | |
100.00% |
56 / 56 |
login | |
100.00% |
1 / 1 |
2 | |
100.00% |
8 / 8 |
|||
logout | n/a |
0 / 0 |
1 | n/a |
0 / 0 |
|||||
register | |
100.00% |
1 / 1 |
8 | |
100.00% |
48 / 48 |
1 | <?php |
2 | |
3 | namespace App\Controller; |
4 | |
5 | use App\Core\Controller; |
6 | use App\Core\DB\DB; |
7 | use App\Core\Form; |
8 | use function App\Core\I18n\_m; |
9 | use App\Core\Log; |
10 | use App\Core\VisibilityScope; |
11 | use App\Entity\Follow; |
12 | use App\Entity\GSActor; |
13 | use App\Entity\LocalUser; |
14 | use App\Entity\Note; |
15 | use App\Security\Authenticator; |
16 | use app\Util\Common; |
17 | use App\Util\Exception\EmailTakenException; |
18 | use App\Util\Exception\NicknameTakenException; |
19 | use App\Util\Exception\ServerException; |
20 | use App\Util\Form\FormFields; |
21 | use App\Util\Nickname; |
22 | use Doctrine\DBAL\Exception\UniqueConstraintViolationException; |
23 | use Symfony\Component\Form\Extension\Core\Type\EmailType; |
24 | use Symfony\Component\Form\Extension\Core\Type\SubmitType; |
25 | use Symfony\Component\Form\Extension\Core\Type\TextType; |
26 | use Symfony\Component\HttpFoundation\Request; |
27 | use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; |
28 | use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; |
29 | use Symfony\Component\Validator\Constraints\Length; |
30 | use Symfony\Component\Validator\Constraints\NotBlank; |
31 | |
32 | class Security extends Controller |
33 | { |
34 | /** |
35 | * Log a user in |
36 | */ |
37 | public function login(AuthenticationUtils $authenticationUtils) |
38 | { |
39 | if ($this->getUser()) { |
40 | return $this->redirectToRoute('main_all'); |
41 | } |
42 | |
43 | // get the login error if there is one |
44 | $error = $authenticationUtils->getLastAuthenticationError(); |
45 | // last username entered by the user |
46 | $last_login_id = $authenticationUtils->getLastUsername(); |
47 | |
48 | return [ |
49 | '_template' => 'security/login.html.twig', |
50 | 'last_login_id' => $last_login_id, |
51 | 'error' => $error, |
52 | 'notes_fn' => fn () => Note::getAllNotes(VisibilityScope::$instance_scope), |
53 | ]; |
54 | } |
55 | |
56 | /** |
57 | * @codeCoverageIgnore |
58 | */ |
59 | public function logout() |
60 | { |
61 | throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); |
62 | } |
63 | |
64 | /** |
65 | * Register a user, making sure the nickname is not reserved and |
66 | * possibly sending a confirmation email |
67 | */ |
68 | public function register(Request $request, |
69 | GuardAuthenticatorHandler $guard_handler, |
70 | Authenticator $authenticator) |
71 | { |
72 | $form = Form::create([ |
73 | ['nickname', TextType::class, [ |
74 | 'label' => _m('Nickname'), |
75 | 'help' => _m('Your desired nickname (e.g., j0hnD03)'), |
76 | 'constraints' => [ |
77 | new NotBlank(['message' => _m('Please enter a nickname')]), |
78 | new Length([ |
79 | 'min' => Common::config('nickname', 'min_length'), |
80 | 'minMessage' => _m(['Your nickname must be at least # characters long'], ['count' => Common::config('nickname', 'min_length')]), |
81 | 'max' => Nickname::MAX_LEN, |
82 | 'maxMessage' => _m(['Your nickname must be at most # characters long'], ['count' => Nickname::MAX_LEN]), ]), |
83 | ], |
84 | 'block_name' => 'nickname', |
85 | 'label_attr' => ['class' => 'section-form-label'], |
86 | ]], |
87 | ['email', EmailType::class, [ |
88 | 'label' => _m('Email'), |
89 | 'help' => _m('Desired email for this account (e.g., john@provider.com)'), |
90 | 'constraints' => [ new NotBlank(['message' => _m('Please enter an email') ])], |
91 | 'block_name' => 'email', |
92 | 'label_attr' => ['class' => 'section-form-label'], |
93 | ]], |
94 | FormFields::repeated_password(), |
95 | ['register', SubmitType::class, ['label' => _m('Register')]], |
96 | ], form_options: ['block_prefix' => 'register']); |
97 | |
98 | $form->handleRequest($request); |
99 | |
100 | if ($form->isSubmitted() && $form->isValid()) { |
101 | $data = $form->getData(); |
102 | $data['password'] = $form->get('password')->getData(); |
103 | |
104 | // This will throw the appropriate errors, result ignored |
105 | $user = LocalUser::findByNicknameOrEmail($data['nickname'], $data['email']); |
106 | if ($user !== null) { |
107 | // If we do find something, there's a duplicate |
108 | if ($user->getNickname() == $data['nickname']) { |
109 | throw new NicknameTakenException; |
110 | } else { |
111 | throw new EmailTakenException; |
112 | } |
113 | } |
114 | |
115 | $valid_nickname = Nickname::validate($data['nickname'], check_already_used: false); |
116 | |
117 | try { |
118 | // This already checks if the nickname is being used |
119 | $actor = GSActor::create(['nickname' => $valid_nickname]); |
120 | $user = LocalUser::create([ |
121 | 'nickname' => $valid_nickname, |
122 | 'outgoing_email' => $data['email'], |
123 | 'incoming_email' => $data['email'], |
124 | 'password' => LocalUser::hashPassword($data['password']), |
125 | ]); |
126 | DB::persistWithSameId( |
127 | $actor, |
128 | $user, |
129 | // Self follow |
130 | fn (int $id) => DB::persist(Follow::create(['follower' => $id, 'followed' => $id])) |
131 | ); |
132 | DB::flush(); |
133 | // @codeCoverageIgnoreStart |
134 | } catch (UniqueConstraintViolationException $e) { |
135 | // _something_ was duplicated, but since we already check if nickname is in use, we can't tell what went wrong |
136 | $e = 'An error occurred while trying to register'; |
137 | Log::critical($e . " with nickname: '{$valid_nickname}' and email '{$data['email']}'"); |
138 | throw new ServerException(_m($e)); |
139 | } |
140 | // @codeCoverageIgnoreEnd |
141 | |
142 | // generate a signed url and email it to the user |
143 | if ($_ENV['APP_ENV'] !== 'dev' && Common::config('site', 'use_email')) { |
144 | // @codeCoverageIgnoreStart |
145 | Common::sendVerificationEmail(); |
146 | // @codeCoverageIgnoreEnd |
147 | } else { |
148 | $user->setIsEmailVerified(true); |
149 | } |
150 | |
151 | return $guard_handler->authenticateUserAndHandleSuccess( |
152 | $user, |
153 | $request, |
154 | $authenticator, |
155 | 'main' // firewall name in security.yaml |
156 | ); |
157 | } |
158 | |
159 | return [ |
160 | '_template' => 'security/register.html.twig', |
161 | 'registration_form' => $form->createView(), |
162 | 'notes_fn' => fn () => Note::getAllNotes(VisibilityScope::$instance_scope), |
163 | ]; |
164 | } |
165 | } |