diff options
Diffstat (limited to 'src/controller/UserController.php')
-rw-r--r-- | src/controller/UserController.php | 284 |
1 files changed, 84 insertions, 200 deletions
diff --git a/src/controller/UserController.php b/src/controller/UserController.php index 50e8bf7..f0880bb 100644 --- a/src/controller/UserController.php +++ b/src/controller/UserController.php | |||
@@ -1,7 +1,15 @@ | |||
1 | <?php | 1 | <?php |
2 | // src/controller/PasswordController.php | 2 | // src/controller/UserController.php |
3 | // | 3 | // |
4 | // test mot de passe et captcha | 4 | /* actuellement un fourre-tout de méthodes en rapport avec les utilisateurs |
5 | pour l'améliorer on pourrait appliquer le principe de reponsabilité unique | ||
6 | => UserController devrait se limiter à gérer des requêtes et réponses (changement transparent pour le routeur) | ||
7 | il instancierait un classe correspondant au formulaire (prend POST et SESSION) et une classe "métier" UserService | ||
8 | => UserService contiendrait des méthodes utilisant l'objet formulaire pour agir sur la BDD | ||
9 | => les formulaires peuvent tenir dans une classe "UserUpdateForm" avec des stratégies ou plusieurs, les données y sont validées | ||
10 | => il est aussi possible de découper UserController en contrôleurs par fonctionnalité: | ||
11 | Auth (connexion, deconnexion), User (infos, choix photo), Account (créer, supprimer compte), Avatar (upload photo...) | ||
12 | */ | ||
5 | 13 | ||
6 | declare(strict_types=1); | 14 | declare(strict_types=1); |
7 | 15 | ||
@@ -11,6 +19,7 @@ use App\Entity\Log; | |||
11 | 19 | ||
12 | class UserController | 20 | class UserController |
13 | { | 21 | { |
22 | // account | ||
14 | static public function existUsers(EntityManager $entityManager): bool | 23 | static public function existUsers(EntityManager $entityManager): bool |
15 | { | 24 | { |
16 | // optimiser ça si possible, on veut juste savoir si la table est vide ou non | 25 | // optimiser ça si possible, on veut juste savoir si la table est vide ou non |
@@ -28,71 +37,34 @@ class UserController | |||
28 | } | 37 | } |
29 | } | 38 | } |
30 | 39 | ||
40 | // account | ||
31 | static public function createUser(EntityManager $entityManager) | 41 | static public function createUser(EntityManager $entityManager) |
32 | { | 42 | { |
33 | // test mauvais paramètres | ||
34 | if(!isset($_POST['login']) || empty($_POST['login']) | ||
35 | || !isset($_POST['password']) || empty($_POST['password']) | ||
36 | || !isset($_POST['password_confirmation']) || empty($_POST['password_confirmation']) | ||
37 | || !isset($_POST['create_user_hidden']) || !empty($_POST['create_user_hidden'])) | ||
38 | { | ||
39 | header('Location: ' . new URL); | ||
40 | die; | ||
41 | } | ||
42 | |||
43 | unset($_SESSION['user']); | 43 | unset($_SESSION['user']); |
44 | 44 | ||
45 | $captcha_solution = (isset($_SESSION['captcha']) && is_int($_SESSION['captcha'])) ? $_SESSION['captcha'] : 0; | 45 | $form = new FormValidation($_POST, 'create_user'); |
46 | $captcha_try = isset($_POST['captcha']) ? Captcha::controlInput($_POST['captcha']) : 0; | ||
47 | unset($_SESSION['captcha']); | ||
48 | 46 | ||
47 | $url = new URL; | ||
49 | $error = ''; | 48 | $error = ''; |
50 | if($captcha_try == 0) | 49 | if($form->validate()){ |
51 | { | 50 | $password = password_hash($_POST['password'], PASSWORD_DEFAULT); |
52 | $error = 'error_non_valid_captcha'; | 51 | $user = new App\Entity\User($_POST['login'], $password); |
53 | } | 52 | $entityManager->persist($user); |
54 | elseif($captcha_solution == 0) // ne peut pas arriver, si? | 53 | $entityManager->flush(); |
55 | { | ||
56 | $error = 'captcha_server_error'; | ||
57 | } | ||
58 | elseif($captcha_try != $captcha_solution) // le test! | ||
59 | { | ||
60 | $error = 'bad_solution_captcha'; | ||
61 | } | 54 | } |
62 | elseif($_POST['password'] !== $_POST['password_confirmation']) | 55 | else{ |
63 | { | 56 | $error = $form->getErrors()[0]; // la 1ère erreur sera affichée |
64 | $error = 'different_passwords'; | ||
65 | } | 57 | } |
66 | else | 58 | |
67 | { | 59 | if(!empty($error)){ |
68 | $login = self::removeSpacesTabsCRLF(htmlspecialchars($_POST['login'])); | 60 | $url->addParams(['error' => $error]); |
69 | $password = self::removeSpacesTabsCRLF(htmlspecialchars($_POST['password'])); | ||
70 | |||
71 | // self::removeSpacesTabsCRLF prévient la validation par erreur d'une chaine "vide" | ||
72 | |||
73 | // conformité | ||
74 | if(!empty($password) && !empty($login) | ||
75 | && $password === $_POST['password'] && $login === $_POST['login']) | ||
76 | { | ||
77 | // enregistrement et redirection | ||
78 | $password = password_hash($password, PASSWORD_DEFAULT); | ||
79 | $user = new App\Entity\User($login, $password); | ||
80 | $entityManager->persist($user); | ||
81 | $entityManager->flush(); | ||
82 | |||
83 | header('Location: ' . new URL); | ||
84 | die; | ||
85 | } | ||
86 | else{ | ||
87 | $error = 'forbidden_characters'; | ||
88 | } | ||
89 | } | 61 | } |
90 | 62 | ||
91 | $url = empty($error) ? new URL : new URL(['error' => $error]); | ||
92 | header('Location: ' . $url); | 63 | header('Location: ' . $url); |
93 | die; | 64 | die; |
94 | } | 65 | } |
95 | 66 | ||
67 | // auth | ||
96 | static public function connect(EntityManager $entityManager): void | 68 | static public function connect(EntityManager $entityManager): void |
97 | { | 69 | { |
98 | if($_SESSION['admin']) // déjà connecté? | 70 | if($_SESSION['admin']) // déjà connecté? |
@@ -100,74 +72,52 @@ class UserController | |||
100 | header('Location: ' . new URL); | 72 | header('Location: ' . new URL); |
101 | die; | 73 | die; |
102 | } | 74 | } |
103 | |||
104 | $_SESSION['user'] = ''; | 75 | $_SESSION['user'] = ''; |
105 | $_SESSION['admin'] = false; | 76 | $_SESSION['admin'] = false; |
106 | 77 | ||
107 | $captcha_solution = (isset($_SESSION['captcha']) && is_int($_SESSION['captcha'])) ? $_SESSION['captcha'] : 0; | 78 | $form = new FormValidation($_POST, 'connection'); |
108 | $captcha_try = isset($_POST['captcha']) ? Captcha::controlInput($_POST['captcha']) : 0; | ||
109 | unset($_SESSION['captcha']); | ||
110 | 79 | ||
111 | $error = ''; | 80 | $error = ''; |
112 | if($captcha_try == 0) | 81 | if($form->validate()){ |
113 | { | 82 | // à mettre dans une classe métier UserService, Authentication, AuthService? |
114 | $error = 'error_non_valid_captcha'; | 83 | $user = self::getUser($_POST['login'], $entityManager); |
115 | } | 84 | if(!empty($user) && $_POST['login'] === $user->getLogin() && password_verify($_POST['password'], $user->getPassword())) |
116 | elseif($captcha_solution == 0) // pas censé se produire | ||
117 | { | ||
118 | $error = 'captcha_server_error'; | ||
119 | } | ||
120 | elseif($captcha_try != $captcha_solution) // le test! | ||
121 | { | ||
122 | $error = 'bad_solution_captcha'; | ||
123 | } | ||
124 | elseif(!isset($_POST['login']) || empty($_POST['login']) | ||
125 | || !isset($_POST['password']) || empty($_POST['password'])) | ||
126 | { | ||
127 | $error = 'bad_login_or_password'; | ||
128 | } | ||
129 | else // c'est OK | ||
130 | { | ||
131 | $login = trim($_POST['login']); | ||
132 | $password = trim($_POST['password']); | ||
133 | $user = self::getUser($login, $entityManager); | ||
134 | |||
135 | // enregistrement et redirection | ||
136 | if(!empty($user) && $login === $user->getLogin() && password_verify($password, $user->getPassword())) | ||
137 | { | 85 | { |
138 | $log = new Log(true); | 86 | $log = new Log(true); |
139 | $entityManager->persist($log); | ||
140 | $entityManager->flush(); | ||
141 | 87 | ||
142 | // protection fixation de session, si l'attaquant crée un cookie de session, il est remplacé | 88 | // protection fixation de session, si l'attaquant crée un cookie de session, il est remplacé |
143 | session_regenerate_id(true); | 89 | session_regenerate_id(true); |
144 | 90 | $_SESSION['user'] = $_POST['login']; | |
145 | $_SESSION['user'] = $login; | ||
146 | $_SESSION['admin'] = true; | 91 | $_SESSION['admin'] = true; |
147 | 92 | ||
148 | $url = new URL(isset($_GET['from']) ? ['page' => $_GET['from']] : []); | 93 | $url = new URL(isset($_GET['from']) ? ['page' => $_GET['from']] : []); |
149 | isset($_GET['id']) ? $url->addParams(['id' => $_GET['id']]) : ''; | 94 | isset($_GET['id']) ? $url->addParams(['id' => $_GET['id']]) : ''; |
150 | header('Location: ' . $url); | ||
151 | die; | ||
152 | } | 95 | } |
153 | else | 96 | else |
154 | { | 97 | { |
155 | $log = new Log(false); | 98 | $log = new Log(false); |
156 | $entityManager->persist($log); | ||
157 | $entityManager->flush(); | ||
158 | $error = 'bad_login_or_password'; | 99 | $error = 'bad_login_or_password'; |
159 | } | 100 | } |
101 | $entityManager->persist($log); | ||
102 | $entityManager->flush(); | ||
103 | } | ||
104 | else{ | ||
105 | $error = $form->getErrors()[0]; // la 1ère erreur sera affichée | ||
106 | } | ||
107 | |||
108 | if(!empty($error)){ | ||
109 | sleep(1); // défense basique à la force brute | ||
110 | $url = new URL(['page' => 'connexion']); | ||
111 | isset($_GET['from']) ? $url->addParams(['from' => $_GET['from']]) : null; | ||
112 | isset($_GET['id']) ? $url->addParams(['id' => $_GET['id']]) : null; | ||
113 | $url->addParams(['error' => $error]); | ||
160 | } | 114 | } |
161 | 115 | ||
162 | // tous les cas sauf connexion réussie | ||
163 | sleep(1); // défense basique à la force brute | ||
164 | $url = new URL(isset($_GET['from']) ? ['page' => 'connexion', 'from' => $_GET['from']] : []); | ||
165 | isset($_GET['id']) ? $url->addParams(['id' => $_GET['id']]) : ''; | ||
166 | !empty($error) ? $url->addParams(['error' => $error]) : ''; | ||
167 | header('Location: ' . $url); | 116 | header('Location: ' . $url); |
168 | die; | 117 | die; |
169 | } | 118 | } |
170 | 119 | ||
120 | // auth | ||
171 | static public function disconnect(): void | 121 | static public function disconnect(): void |
172 | { | 122 | { |
173 | // nettoyage complet | 123 | // nettoyage complet |
@@ -183,154 +133,87 @@ class UserController | |||
183 | die; | 133 | die; |
184 | } | 134 | } |
185 | 135 | ||
136 | // user | ||
186 | static public function updateUsername(EntityManager $entityManager): void | 137 | static public function updateUsername(EntityManager $entityManager): void |
187 | { | 138 | { |
188 | if(!$_SESSION['admin']) // superflux, fait dans le routeur | 139 | if(!$_SESSION['admin']){ // superflux, fait dans le routeur |
189 | { | ||
190 | self::disconnect(); | 140 | self::disconnect(); |
191 | } | 141 | } |
192 | 142 | ||
193 | $captcha_solution = (isset($_SESSION['captcha']) && is_int($_SESSION['captcha'])) ? $_SESSION['captcha'] : 0; | ||
194 | $captcha_try = isset($_POST['captcha']) ? Captcha::controlInput($_POST['captcha']) : 0; | ||
195 | unset($_SESSION['captcha']); | ||
196 | |||
197 | $url = new URL(['page' => 'user_edit']); | 143 | $url = new URL(['page' => 'user_edit']); |
198 | isset($_GET['from']) ? $url->addParams(['from' => $_GET['from']]) : null; | 144 | isset($_GET['from']) ? $url->addParams(['from' => $_GET['from']]) : null; |
199 | 145 | ||
146 | $form = new FormValidation($_POST, 'username_update'); | ||
147 | |||
200 | $error = ''; | 148 | $error = ''; |
201 | if(!isset($_POST['old_login']) || empty($_POST['old_login']) | 149 | if($form->validate()){ |
202 | || !isset($_POST['password']) || empty($_POST['password']) | 150 | // à mettre dans une classe métier UserService? |
203 | || !isset($_POST['new_login']) || empty($_POST['new_login']) | 151 | $user = self::getUser($_POST['login'], $entityManager); |
204 | || !isset($_POST['modify_username_hidden']) || !empty($_POST['modify_username_hidden'])) | 152 | if(password_verify($_POST['password'], $user->getPassword())){ |
205 | { | 153 | $user->setLogin($_POST['new_login']); |
206 | $error = 'bad_login_or_password'; | 154 | $entityManager->flush(); |
207 | } | 155 | $_SESSION['user'] = $_POST['new_login']; |
208 | elseif($captcha_try != $captcha_solution) // le test! | ||
209 | { | ||
210 | $error = 'bad_solution_captcha'; | ||
211 | } | ||
212 | else | ||
213 | { | ||
214 | // sécurisation de la saisie | ||
215 | $old_login = $_POST['old_login']; | ||
216 | $password = $_POST['password']; | ||
217 | $new_login = self::removeSpacesTabsCRLF(htmlspecialchars($_POST['new_login'])); | ||
218 | // removeSpacesTabsCRLF pour éviter d'enregistrer une chaîne vide | ||
219 | 156 | ||
220 | // tests de conformité | 157 | $url->addParams(['success_username' => 'new_login']); |
221 | if($old_login !== $_POST['old_login'] || $password !== $_POST['password'] || $new_login !== $_POST['new_login']) | 158 | $error = ''; |
222 | { | ||
223 | $error = 'forbidden_characters'; | ||
224 | } | 159 | } |
225 | elseif($old_login !== $_SESSION['user']){ | 160 | else{ |
226 | $error = 'bad_login_or_password'; | 161 | $error = 'bad_login_or_password'; |
227 | } | 162 | } |
228 | elseif($old_login === $new_login){ | ||
229 | $error = 'same_username_as_before'; | ||
230 | } | ||
231 | else | ||
232 | { | ||
233 | $user = self::getUser($old_login, $entityManager); | ||
234 | |||
235 | if(password_verify($password, $user->getPassword())) | ||
236 | { | ||
237 | $user->setLogin($new_login); | ||
238 | $entityManager->flush(); | ||
239 | $_SESSION['user'] = $new_login; | ||
240 | |||
241 | $url->addParams(['success_login' => 'new_login']); | ||
242 | $error = ''; | ||
243 | } | ||
244 | else | ||
245 | { | ||
246 | $error = 'bad_login_or_password'; | ||
247 | } | ||
248 | } | ||
249 | } | 163 | } |
250 | 164 | else{ | |
165 | $error = $form->getErrors()[0]; // la 1ère erreur sera affichée | ||
166 | } | ||
167 | |||
251 | if(!empty($error)){ | 168 | if(!empty($error)){ |
252 | sleep(1); | 169 | sleep(1); |
253 | $url->addParams(['error_login' => $error]); | 170 | $url->addParams(['error_username' => $error]); |
254 | } | 171 | } |
255 | |||
256 | header('Location: ' . $url); | 172 | header('Location: ' . $url); |
257 | die; | 173 | die; |
258 | } | 174 | } |
259 | 175 | ||
176 | // user | ||
260 | static public function updatePassword(EntityManager $entityManager): void | 177 | static public function updatePassword(EntityManager $entityManager): void |
261 | { | 178 | { |
262 | if(!$_SESSION['admin']) // superflux, fait dans le routeur | 179 | if(!$_SESSION['admin']){ // superflux, fait dans le routeur |
263 | { | ||
264 | self::disconnect(); | 180 | self::disconnect(); |
265 | } | 181 | } |
266 | 182 | ||
267 | $captcha_solution = (isset($_SESSION['captcha']) && is_int($_SESSION['captcha'])) ? $_SESSION['captcha'] : 0; | ||
268 | $captcha_try = isset($_POST['captcha']) ? Captcha::controlInput($_POST['captcha']) : 0; | ||
269 | unset($_SESSION['captcha']); | ||
270 | |||
271 | $url = new URL(['page' => 'user_edit']); | 183 | $url = new URL(['page' => 'user_edit']); |
272 | isset($_GET['from']) ? $url->addParams(['from' => $_GET['from']]) : null; | 184 | isset($_GET['from']) ? $url->addParams(['from' => $_GET['from']]) : null; |
273 | 185 | ||
186 | $form = new FormValidation($_POST, 'password_update'); | ||
187 | |||
274 | $error = ''; | 188 | $error = ''; |
275 | if(!isset($_POST['login']) || empty($_POST['login']) | 189 | if($form->validate()){ |
276 | || !isset($_POST['old_password']) || empty($_POST['old_password']) | 190 | // à mettre dans une classe métier UserService? |
277 | || !isset($_POST['new_password']) || empty($_POST['new_password']) | 191 | $user = self::getUser($_POST['login'], $entityManager); |
278 | || !isset($_POST['modify_password_hidden']) || !empty($_POST['modify_password_hidden'])) | 192 | if(password_verify($_POST['password'], $user->getPassword())){ |
279 | { | 193 | $new_password = password_hash($_POST['new_password'], PASSWORD_DEFAULT); |
280 | $error = 'bad_login_or_password'; | 194 | $user->setPassword($new_password); |
281 | } | 195 | $entityManager->flush(); |
282 | elseif($captcha_try != $captcha_solution) // le test! | ||
283 | { | ||
284 | $error = 'bad_solution_captcha'; | ||
285 | } | ||
286 | else | ||
287 | { | ||
288 | // sécurisation de la saisie | ||
289 | $login = $_POST['login']; | ||
290 | $old_password = $_POST['old_password']; | ||
291 | $new_password = self::removeSpacesTabsCRLF(htmlspecialchars($_POST['new_password'])); | ||
292 | // removeSpacesTabsCRLF pour éviter d'enregistrer une chaîne vide | ||
293 | 196 | ||
294 | // tests de conformité | 197 | $url->addParams(['success_password' => 'new_password']); |
295 | if($login !== $_POST['login'] || $old_password !== $_POST['old_password'] || $new_password !== $_POST['new_password']) | 198 | $error = ''; |
296 | { | ||
297 | $error = 'forbidden_characters'; | ||
298 | } | 199 | } |
299 | elseif($login !== $_SESSION['user']){ | 200 | else{ |
300 | $error = 'bad_login_or_password'; | 201 | $error = 'bad_login_or_password'; |
301 | } | 202 | } |
302 | elseif($old_password === $new_password){ | ||
303 | $error = 'same_password_as_before'; | ||
304 | } | ||
305 | else | ||
306 | { | ||
307 | $user = self::getUser($login, $entityManager); | ||
308 | |||
309 | if(password_verify($old_password, $user->getPassword())) | ||
310 | { | ||
311 | $new_password = password_hash($new_password, PASSWORD_DEFAULT); | ||
312 | $user->setPassword($new_password); | ||
313 | $entityManager->flush(); | ||
314 | |||
315 | $url->addParams(['success_password' => 'new_password']); | ||
316 | $error = ''; | ||
317 | } | ||
318 | else | ||
319 | { | ||
320 | $error = 'bad_login_or_password'; | ||
321 | } | ||
322 | } | ||
323 | } | 203 | } |
324 | 204 | else{ | |
205 | $error = $form->getErrors()[0]; // la 1ère erreur sera affichée | ||
206 | } | ||
207 | |||
325 | if(!empty($error)){ | 208 | if(!empty($error)){ |
326 | sleep(1); | 209 | sleep(1); |
327 | $url->addParams(['error_password' => $error]); | 210 | $url->addParams(['error_password' => $error]); |
328 | } | 211 | } |
329 | |||
330 | header('Location: ' . $url); | 212 | header('Location: ' . $url); |
331 | die; | 213 | die; |
332 | } | 214 | } |
333 | 215 | ||
216 | // dans une classe mère ou un trait après découpage de UserController? | ||
334 | static private function getUser(string $login, EntityManager $entityManager): ?User | 217 | static private function getUser(string $login, EntityManager $entityManager): ?User |
335 | { | 218 | { |
336 | $users = $entityManager->getRepository('App\Entity\User')->findBy(['login' => $login]); | 219 | $users = $entityManager->getRepository('App\Entity\User')->findBy(['login' => $login]); |
@@ -351,6 +234,7 @@ class UserController | |||
351 | return null; | 234 | return null; |
352 | } | 235 | } |
353 | 236 | ||
237 | // dans une classe Form? | ||
354 | // erreurs à la création des mots de passe | 238 | // erreurs à la création des mots de passe |
355 | static private function removeSpacesTabsCRLF(string $chaine): string | 239 | static private function removeSpacesTabsCRLF(string $chaine): string |
356 | { | 240 | { |