raphael 4 years ago
parent
commit
01e601ec7b
30 changed files with 2251 additions and 67 deletions
  1. 1
    1
      assets/shared/build/js/common/header/header.js
  2. 2
    1
      composer.json
  3. 1
    1
      config/doctrine/Client.orm.xml
  4. 1
    1
      config/doctrine/Person.orm.xml
  5. 1
    1
      config/doctrine/SessionPlan.orm.xml
  6. 1
    1
      config/doctrine/User.User.orm.xml
  7. 3
    4
      config/packages/security.yaml
  8. 7
    3
      config/routes.yaml
  9. 13
    1
      config/services.yaml
  10. 7
    3
      public/build/manifest.json
  11. 1
    1
      src/CaptainLearning/Command/MigrateCommand.php
  12. 2
    1
      src/CaptainLearning/Command/SetupCommand.php
  13. 1
    1
      src/CaptainLearning/Controller/AppController.php
  14. 86
    3
      src/CaptainLearning/Controller/Common/LoginController.php
  15. 32
    0
      src/CaptainLearning/Entity/User/Constraints/password/Password.php
  16. 237
    0
      src/CaptainLearning/Entity/User/Constraints/password/PasswordValidator.php
  17. 1
    1
      src/CaptainLearning/Entity/User/User.php
  18. 90
    0
      src/CaptainLearning/Event/EventSubscriber/CaptainLearningEventSubscriber.php
  19. 85
    0
      src/CaptainLearning/Event/EventSubscriber/PasswordRecoverNotifyUser.php
  20. 69
    0
      src/CaptainLearning/Event/EventSubscriber/UserSuscribeNotifyUser.php
  21. 23
    0
      src/CaptainLearning/Event/Events.php
  22. 1
    0
      src/CaptainLearning/Kernel/AppKernel.php
  23. 1
    1
      src/CaptainLearning/Repository/UserRepository.php
  24. 0
    39
      src/CaptainLearning/Security/UserChecker.php
  25. 3
    2
      templates/common/layout/header/modal/partial/recover.html.twig
  26. 30
    0
      templates/common/setpassword.html.twig
  27. 16
    0
      templates/emails/PasswordRecover.html.twig
  28. 9
    0
      templates/emails/UserSubscribe.html.twig
  29. 1458
    0
      templates/emails/layout/layout.base.html.twig
  30. 69
    1
      translations/messages.fr.xlf

+ 1
- 1
assets/shared/build/js/common/header/header.js View File

@@ -371,7 +371,7 @@ $( document ).ready(function()
371 371
 			}
372 372
 		};
373 373
 		let formLogin = modal.find("form#form-recover");
374
-		//formLogin.cptInitForm(options);
374
+		formLogin.cptInitForm(options);
375 375
 	}
376 376
 
377 377
 

+ 2
- 1
composer.json View File

@@ -22,7 +22,8 @@
22 22
 		"doctrine/annotations" : "~1.6",
23 23
 		"symfony/swiftmailer-bundle" : "^3.2",
24 24
 		"symfony/form" : "~4.1.6",
25
-		"vich/uploader-bundle" : "~1.8"
25
+		"vich/uploader-bundle" : "~1.8.4",
26
+		"defuse/php-encryption" : "~2.2.1"
26 27
 	},
27 28
 	"require-dev" : {
28 29
 		"symfony/profiler-pack" : "1.0.*@dev"

+ 1
- 1
config/doctrine/Client.orm.xml View File

@@ -13,7 +13,7 @@
13 13
 	        <generator strategy="AUTO" />
14 14
 	    </id>
15 15
 
16
-		<one-to-one field="user" target-entity="Logipro\CaptainLearning\Entity\User" inversed-by="clients">
16
+		<one-to-one field="user" target-entity="Logipro\CaptainLearning\Entity\User\User" inversed-by="clients">
17 17
             <join-column name="ref_user" referenced-column-name="user_id" nullable="true" />
18 18
         </one-to-one>
19 19
 		<one-to-one field="company" target-entity="Logipro\CaptainLearning\Entity\Company" inversed-by="clients">

+ 1
- 1
config/doctrine/Person.orm.xml View File

@@ -24,7 +24,7 @@
24 24
 		
25 25
 		 <many-to-one
26 26
             field="user"
27
-            target-entity="Logipro\CaptainLearning\Entity\User">
27
+            target-entity="Logipro\CaptainLearning\Entity\User\User">
28 28
             <join-column nullable="true" name="ref_user" referenced-column-name="user_id"/>
29 29
         </many-to-one>
30 30
 

+ 1
- 1
config/doctrine/SessionPlan.orm.xml View File

@@ -39,7 +39,7 @@
39 39
         </many-to-one>
40 40
 		 <many-to-one
41 41
             field="user"
42
-            target-entity="Logipro\CaptainLearning\Entity\User">
42
+            target-entity="Logipro\CaptainLearning\Entity\User\User">
43 43
             <join-column nullable="true" name="ref_user" referenced-column-name="user_id"/>
44 44
         </many-to-one>
45 45
 

config/doctrine/User.orm.xml → config/doctrine/User.User.orm.xml View File

@@ -4,7 +4,7 @@
4 4
     xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
5 5
         http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
6 6
 
7
-	<entity name="Logipro\CaptainLearning\Entity\User"
7
+	<entity name="Logipro\CaptainLearning\Entity\User\User"
8 8
 		table="cpt_user"
9 9
 		repository-class="Logipro\CaptainLearning\Repository\UserRepository">
10 10
 

+ 3
- 4
config/packages/security.yaml View File

@@ -2,8 +2,8 @@
2 2
 security:
3 3
     encoders:
4 4
 #        Symfony\Component\Security\Core\User\User: plaintext
5
-#        MyProject\UserBundle\Entity\User: sha512
6
-        Logipro\CaptainLearning\Entity\User:
5
+#        MyProject\UserBundle\Entity\User\User: sha512
6
+        Logipro\CaptainLearning\Entity\User\User:
7 7
             algorithm: bcrypt
8 8
     providers:
9 9
         default:
@@ -14,11 +14,10 @@ security:
14 14
 #                        password: admin
15 15
 #                        roles: ['AUTRE_ROLE','ROLE_ADMIN']
16 16
             entity:
17
-                class: Logipro\CaptainLearning\Entity\User
17
+                class: Logipro\CaptainLearning\Entity\User\User
18 18
                 property: email
19 19
     firewalls:
20 20
         main:
21
-            user_checker: Logipro\CaptainLearning\Security\UserChecker
22 21
             anonymous: true
23 22
             form_login:
24 23
                 login_path: login

+ 7
- 3
config/routes.yaml View File

@@ -35,9 +35,13 @@ logout:
35 35
   path: /logout
36 36
   controller: \Logipro\CaptainLearning\Controller\Common\LoginController::logout
37 37
 
38
-recover:
39
-  path: /recover
40
-  controller: \Logipro\CaptainLearning\Controller\Common\LoginController::recoverPassword
38
+recoverPassword:
39
+  path: /password/recover
40
+  controller: \Logipro\CaptainLearning\Controller\Common\LoginController::sendRecoverPassword
41
+
42
+definePassword:
43
+  path: /password/define
44
+  controller: \Logipro\CaptainLearning\Controller\Common\LoginController::drawRecoverPassword
41 45
 subscribe:
42 46
   path: /subscribe
43 47
   controller: \Logipro\CaptainLearning\Controller\Common\LoginController::validateSubscribe

+ 13
- 1
config/services.yaml View File

@@ -38,4 +38,16 @@ services:
38 38
   captainSettings:
39 39
     public: true
40 40
     class: Logipro\CaptainLearning\Service\SettingsManager
41
-    arguments: ['@doctrine.orm.entity_manager']
41
+    arguments: ['@doctrine.orm.entity_manager']
42
+
43
+#===============================================================================
44
+# Services d'e-mails.
45
+#===============================================================================
46
+  # Service envoie mail "Récupération de mot de passe" (BACK OFFICE)
47
+  Logipro\CaptainLearning\Event\EventSubscriber\PasswordRecoverNotifyUser:
48
+        # le nom de la variable que l'on utilisera dans le service
49
+        $sender: '%app.notifications.email_sender%'
50
+
51
+  Logipro\CaptainLearning\Event\EventSubscriber\UserSuscribeNotifyUser:
52
+        # le nom de la variable que l'on utilisera dans le service
53
+        $sender: '%app.notifications.email_sender%'

+ 7
- 3
public/build/manifest.json View File

@@ -5,8 +5,12 @@
5 5
   "build/list.js": "http://localhost:3000/CaptainLearning/build/list.js",
6 6
   "build/captcha.js": "http://localhost:3000/CaptainLearning/build/captcha.js",
7 7
   "build/manifest.js": "http://localhost:3000/CaptainLearning/build/manifest.js",
8
-  "build/images/billets.svg": "http://localhost:3000/CaptainLearning/build/images/billets.847328cb.svg",
8
+  "build/images/add_hover.svg": "http://localhost:3000/CaptainLearning/build/images/add_hover.3acf793e.svg",
9
+  "build/fonts/ionicons.eot?v=2.0.0": "http://localhost:3000/CaptainLearning/build/fonts/ionicons.2c2ae068.eot",
10
+  "build/fonts/ionicons.ttf?v=2.0.0": "http://localhost:3000/CaptainLearning/build/fonts/ionicons.24712f6c.ttf",
11
+  "build/fonts/ionicons.woff?v=2.0.0": "http://localhost:3000/CaptainLearning/build/fonts/ionicons.05acfdb5.woff",
9 12
   "build/images/apple-touch-icon.png": "http://localhost:3000/CaptainLearning/build/images/apple-touch-icon.b4c8c50b.png",
13
+  "build/images/avatar.png": "http://localhost:3000/CaptainLearning/build/images/avatar.b6a1e70d.png",
10 14
   "build/images/captain-sign-in-organisme-de-formation.jpg": "http://localhost:3000/CaptainLearning/build/images/captain-sign-in-organisme-de-formation.9fb0bb2d.jpg",
11 15
   "build/images/formations-achat-transport-logistique-internationale.jpg": "http://localhost:3000/CaptainLearning/build/images/formations-achat-transport-logistique-internationale.3668aafa.jpg",
12 16
   "build/images/formations-commerce-vente.jpg": "http://localhost:3000/CaptainLearning/build/images/formations-commerce-vente.f8682e5a.jpg",
@@ -49,10 +53,10 @@
49 53
   "build/images/add.svg": "http://localhost:3000/CaptainLearning/build/images/add.b62afaf5.svg",
50 54
   "build/images/add_formation.svg": "http://localhost:3000/CaptainLearning/build/images/add_formation.cdc2aa51.svg",
51 55
   "build/images/add_formation_maincolor.svg": "http://localhost:3000/CaptainLearning/build/images/add_formation_maincolor.47366174.svg",
52
-  "build/images/add_hover.svg": "http://localhost:3000/CaptainLearning/build/images/add_hover.3acf793e.svg",
56
+  "build/images/ionicons.svg?v=2.0.0": "http://localhost:3000/CaptainLearning/build/images/ionicons.621bd386.svg",
53 57
   "build/images/add_souhait.svg": "http://localhost:3000/CaptainLearning/build/images/add_souhait.935a729a.svg",
54 58
   "build/images/add_souhait_maincolor.svg": "http://localhost:3000/CaptainLearning/build/images/add_souhait_maincolor.2ebb95d1.svg",
55
-  "build/images/avatar.png": "http://localhost:3000/CaptainLearning/build/images/avatar.b6a1e70d.png",
59
+  "build/images/billets.svg": "http://localhost:3000/CaptainLearning/build/images/billets.847328cb.svg",
56 60
   "build/images/billetsNoir.svg": "http://localhost:3000/CaptainLearning/build/images/billetsNoir.514fdca3.svg",
57 61
   "build/images/close.svg": "http://localhost:3000/CaptainLearning/build/images/close.14776e9e.svg",
58 62
   "build/images/close_hover.svg": "http://localhost:3000/CaptainLearning/build/images/close_hover.a2e05323.svg",

+ 1
- 1
src/CaptainLearning/Command/MigrateCommand.php View File

@@ -1,7 +1,7 @@
1 1
 <?php
2 2
 namespace Logipro\CaptainLearning\Command;
3 3
 
4
-use Logipro\CaptainLearning\Entity\User;
4
+use Logipro\CaptainLearning\Entity\User\User;
5 5
 use Doctrine\Common\Persistence\ManagerRegistry;
6 6
 use Symfony\Component\Console\Input\InputInterface;
7 7
 use Logipro\CaptainLearning\Service\SettingsManager;

+ 2
- 1
src/CaptainLearning/Command/SetupCommand.php View File

@@ -1,7 +1,7 @@
1 1
 <?php
2 2
 namespace Logipro\CaptainLearning\Command;
3 3
 
4
-use Logipro\CaptainLearning\Entity\User;
4
+use Logipro\CaptainLearning\Entity\User\User;
5 5
 use Doctrine\Common\Persistence\ManagerRegistry;
6 6
 use Symfony\Component\Console\Input\InputInterface;
7 7
 use Logipro\CaptainLearning\Service\SettingsManager;
@@ -45,6 +45,7 @@ class SetupCommand extends ContainerAwareCommand
45 45
 		$settings->install('elasticsearch_url', 'http://localhost:9200/', 'label_setting_elasticsearch_url','string');
46 46
 		$settings->install('elasticsearch_cluster_uuid', 'rBeC9PaRRpqwtWnJV12Gog', 'label_setting_elasticsearch_cluster_uuid','string');
47 47
 		$settings->install('elasticsearch_captain_learning_index_name', 'captainlearning', 'label_setting_elasticsearch_captain_learning_index_name','string');
48
+		$settings->install('password_policy', 'length>1', 'label_setting_password_policy_name','string');
48 49
 
49 50
 		$output->writeln('<info>Les paramètres de configuration ont été créés ou mis à jour.</info>');
50 51
 	}

+ 1
- 1
src/CaptainLearning/Controller/AppController.php View File

@@ -1,7 +1,7 @@
1 1
 <?php
2 2
 namespace Logipro\CaptainLearning\Controller;
3 3
 
4
-use Logipro\CaptainLearning\Entity\User;
4
+use Logipro\CaptainLearning\Entity\User\User;
5 5
 use Logipro\CaptainLearning\Entity\Company;
6 6
 use Symfony\Component\HttpFoundation\Request;
7 7
 use Symfony\Component\HttpFoundation\Response;

+ 86
- 3
src/CaptainLearning/Controller/Common/LoginController.php View File

@@ -1,20 +1,24 @@
1 1
 <?php
2 2
 namespace Logipro\CaptainLearning\Controller\Common;
3 3
 
4
-use Logipro\CaptainLearning\Entity\User;
4
+use Logipro\CaptainLearning\Event\Events;
5
+use Logipro\CaptainLearning\Entity\User\User;
5 6
 use Symfony\Component\HttpFoundation\Request;
6 7
 use Symfony\Component\HttpFoundation\Response;
8
+
7 9
 use Symfony\Component\HttpFoundation\JsonResponse;
10
+use Symfony\Component\EventDispatcher\GenericEvent;
8 11
 use Logipro\CaptainLearning\Controller\AppController;
9
-
10 12
 use Logipro\CaptainLearning\Entity\Type\EnumRoleType;
11 13
 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
12 14
 use Logipro\CaptainLearning\Entity\Type\EnumUserStatusType;
13 15
 use Logipro\CaptainLearning\Entity\Type\EnumUserCivilityType;
16
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
14 17
 use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
15 18
 use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
16 19
 use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
17 20
 use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
21
+use Logipro\CaptainLearning\Entity\Category\Constraints\BuisnessCode\PasswordValidator;
18 22
 
19 23
 class LoginController extends AppController
20 24
 {
@@ -81,6 +85,11 @@ class LoginController extends AppController
81 85
 	{
82 86
 		try
83 87
 		{
88
+			if (!$request->isXmlHttpRequest())
89
+			{
90
+				throw new \Exception();
91
+			}
92
+
84 93
 			// test CGV
85 94
 			if (!$request->request->get("accepter_cgv"))
86 95
 			{
@@ -126,9 +135,83 @@ class LoginController extends AppController
126 135
 		return new JsonResponse($result);
127 136
 	}
128 137
 
129
-	public function recoverPassword()
138
+	public function sendRecoverPassword(Request $request,EventDispatcherInterface $eventDispatcher)
139
+	{
140
+		try
141
+		{
142
+			if (!$request->isXmlHttpRequest())
143
+			{
144
+				return;
145
+			}
146
+
147
+			$email = $request->request->get('email');
148
+
149
+			// vérifie si l'email correspond à un utilisateur
150
+			$manager = $this->getDoctrine()->getManager();
151
+
152
+			$repository = $manager->getRepository(User::class);
153
+			$user = $repository->findOneBy(array('email' => $email));
154
+			if ($user)
155
+			{
156
+				// événement d'inscription
157
+				//On déclenche l'event
158
+				$event = new GenericEvent($user);
159
+				$eventDispatcher->dispatch(Events::USER_RECOVER_PASSWORD, $event);
160
+			}
161
+
162
+			//remonte l'information à l'utilisateur
163
+			$result['result'] = true;
164
+
165
+			$result["erreur-msg"] = "Le mail a été envoyé";
166
+		}
167
+		catch (\Exception $exp)
168
+		{
169
+			$result['result'] = false;
170
+			$result["error"] = $exp->getMessage();
171
+			$result["erreur-msg"] = "Une erreur s'est produite";
172
+		}
173
+		return new JsonResponse($result);
174
+	}
175
+
176
+	public function drawRecoverPassword(Request $request)
130 177
 	{
178
+		try
179
+		{
180
+			// si le formulaire a été envoyé
181
+			$token = $request->request->get('token');
182
+			$tokenData = unserialize($token);
183
+			$manager = $this->getDoctrine()->getManager();
184
+			$repository = $manager->getRepository(User::class);
185
+			$user = $repository->findOneBy(array('' => $this->arrayGet($tokenData,'')));
186
+			if (!$user)
187
+			{
188
+				return;
189
+			}
190
+
191
+			if ($request->isMethod('POST'))
192
+			{
193
+				$password = $request->request('password');
194
+				$passwordCopy = $request->request('password_copy');
195
+
196
+				// erreur entre les MDP
197
+				if ($password != $passwordCopy)
198
+				{
199
+
200
+				}
201
+			}
202
+
203
+			$user->setPassword($user->encodePassword($password));
204
+			$manager->persist($user);
205
+			$manager->flush();
206
+		}
207
+		catch (\Exception $exp)
208
+		{
209
+
210
+		}
131 211
 
212
+		// affiche la vue pour définir le mot de passe
213
+		$parameters = array('token' => $token,'password_policity' => PasswordValidator::getPolicy($this->get('captainSettings')));
214
+		return $this->render('common/setpassword.html.twig',$parameters);
132 215
 	}
133 216
 }
134 217
 ?>

+ 32
- 0
src/CaptainLearning/Entity/User/Constraints/password/Password.php View File

@@ -0,0 +1,32 @@
1
+<?php
2
+namespace Logipro\CaptainLearning\Entity\Category\Constraints\BuisnessCode;
3
+
4
+use Symfony\Component\Validator\Constraint;
5
+
6
+use Logipro\CaptainLearning\Entity\User\Constraints\Level\PasswordValidator;
7
+
8
+class Password extends Constraint
9
+{
10
+	public $message = 'This value is already used.';
11
+
12
+	/**
13
+	 * The validator must be defined as a service with this name.
14
+	 *
15
+	 * @return string
16
+	 */
17
+	public function validatedBy()
18
+	{
19
+		return PasswordValidator::class;
20
+	}
21
+
22
+
23
+	public function getRequiredOptions()
24
+	{
25
+	}
26
+
27
+	public function getTargets()
28
+	{
29
+		return self::CLASS_CONSTRAINT;
30
+	}
31
+}
32
+?>

+ 237
- 0
src/CaptainLearning/Entity/User/Constraints/password/PasswordValidator.php View File

@@ -0,0 +1,237 @@
1
+<?php
2
+namespace Logipro\CaptainLearning\Entity\Category\Constraints\BuisnessCode;
3
+
4
+use Symfony\Component\Validator\Constraint;
5
+use Symfony\Component\Translation\Translator;
6
+
7
+use Logipro\CaptainLearning\Service\SettingsManager;
8
+use Symfony\Component\Validator\ConstraintValidator;
9
+use Logipro\CaptainLearning\Entity\Category\Category;
10
+
11
+class PasswordValidator extends ConstraintValidator
12
+{
13
+	private $settingsManager = null;
14
+
15
+	public function validate(Category $entity, Constraint $constraint,Translator $translator,SettingsManager $settingsManager)
16
+	{
17
+		$this->settingsManager = $settingsManager;
18
+		$erroMsg = self::renderError($translator,$entity->getPassword());
19
+		if ($erroMsg != "")
20
+		{
21
+			$this->context->buildViolation($constraint->message)
22
+			->atPath('password')
23
+			->setParameter('{{ value }}', $erroMsg)
24
+			->addViolation();
25
+		}
26
+	}
27
+
28
+	/**
29
+	 * Retourne si la police est validée ou non.
30
+	 * @param string $password Mot de passe à checker
31
+	 * @return bool
32
+	 */
33
+	public static function renderError(Translator $translator,SettingsManager $settingsManager,$password)
34
+	{
35
+		$result = self::checkPolicy($settingsManager,$password);
36
+
37
+		// récupère les erreurs textuelle, et les ordonne pour l'affichage
38
+		$errorsText = array_keys($result);
39
+		uasort($errorsText, array('CPTPassword',"sortErrors"));
40
+		$errors = array();
41
+		foreach ($errorsText as $name)
42
+		{
43
+			$value = $result[$name];
44
+			if (!$value)
45
+			{
46
+				$errors[] = self::i18n($name);
47
+			}
48
+		}
49
+
50
+		// parcours les erreurs pour constituer le message
51
+		$message = "";
52
+		if (!empty($errors))
53
+		{
54
+			$last = "";
55
+			if (count($errors) > 1)
56
+			{
57
+				$last = array_pop($errors);
58
+			}
59
+
60
+			$message = $prefixe = "";
61
+			foreach ($errors as $code => $msg)
62
+			{
63
+				// sépérateur entre les caractéristiques: ,
64
+				if ($message != "")
65
+				{
66
+					$message .= ", ";
67
+				}
68
+				$message .= $prefixe . $translator->trans($msg);
69
+
70
+				// préfixe dans le cas ou on est après la length qui est placé en premier
71
+				if ($code == "length")
72
+				{
73
+					$prefixe = $translator->trans(label_password_error_mask_with);
74
+				}
75
+				else
76
+				{
77
+					$prefixe = "";
78
+				}
79
+			}
80
+			$message = $translator->trans('label_password_error_mask') . $message;
81
+
82
+			if ($last != "")
83
+			{
84
+				$message .= sprintf($translator->trans('label_password_error_mask_link'),$translator->trans($last));
85
+			}
86
+		}
87
+		return $message;
88
+	}
89
+
90
+	/*
91
+	 * ordonne les erreurs pour l'affichage textuel
92
+	 *
93
+	 * @param string
94
+	 * @param string
95
+	 *
96
+	 * @return 0/1/-1
97
+	 */
98
+	public static function sortErrors($a,$b)
99
+	{
100
+		$order = array("length","lowercase","uppercase","digit","letter","special");
101
+
102
+		$order1 = array_search($a,$order);
103
+		$order2 = array_search($b,$order);
104
+
105
+		if ($order1 == $order2) {
106
+			return 0;
107
+		}
108
+		return ($order1 < $order2) ? -1 : 1;
109
+	}
110
+
111
+	/**
112
+	 * retourne le texte a partir de l'erreur
113
+	 *
114
+	 * @param string $name
115
+	 *
116
+	 * @return string
117
+	 */
118
+	private static function i18n(SettingsManager $manager,$name)
119
+	{
120
+		$policy = self::getPolicy($manager);
121
+
122
+		$valuesMultiple = array
123
+		(
124
+			"length" => 'label_password_mask_has_caracters',
125
+			"digit" => 'label_password_mask_has_numbers',
126
+			"letter" => 'label_password_mask_has_letters',
127
+			"lowercase" => 'label_password_has_lowercases',
128
+			"uppercase" => 'label_password_has_uppercases',
129
+			"special" => 'label_password_has_specials',
130
+		);
131
+		$valuesSingle = array
132
+		(
133
+			"length" => 'label_password_has_caracter',
134
+			"digit" => 'label_password_has_number',
135
+			"letter" => 'label_password_has_letter',
136
+			"lowercase" => 'label_password_has_lowercase',
137
+			"uppercase" => 'label_password_uppercase',
138
+			"special" => 'label_password_special',
139
+		);
140
+
141
+		$value = $policy[$name];
142
+		if ($value > 1)
143
+		{
144
+			return sprintf($valuesMultiple[$name],$policy[$name]);
145
+		}
146
+		return sprintf($valuesSingle[$name],$policy[$name]);
147
+	}
148
+
149
+	/**
150
+	 * Retourne si la police est validée ou non.
151
+	 * @param string $password Mot de passe à checker
152
+	 * @return bool
153
+	 */
154
+	public static function isPolicyChecked(SettingsManager $manager,$password)
155
+	{
156
+		$result = self::checkPolicy($manager,$password);
157
+		
158
+		foreach ($result as $value) {
159
+			if (!$value) {
160
+				return false;
161
+			}
162
+		}
163
+		
164
+		return true;
165
+	}
166
+	
167
+	/**
168
+	 * Retourne un tableau de booléens qui indique si les règles
169
+	 * de la police sont validées ou non par le mot de passe.
170
+	 * 
171
+	 * Attention cette méthode est également définie dans UTIPasswordChecker.js.
172
+	 * Les deux versions doivent être maintenues identiques.
173
+	 * 
174
+	 * Il n'est pas souhaitable de factoriser la version PHP et JS
175
+	 * via des appels AJAX, car cela produirait beaucoup de transmissions
176
+	 * du mot de passe entre client et serveur, ce qui n'est pas bon
177
+	 * pour la sécurité.
178
+	 * 
179
+	 * @param string $password Mot de passe à checker
180
+	 * @return array
181
+	 */
182
+	public static function checkPolicy(SettingsManager $manager,$password)
183
+	{
184
+		$result = array();
185
+		
186
+		$policy = self::getPolicy($manager);
187
+		
188
+		foreach ($policy as $name => $value)
189
+		{
190
+			switch ($name)
191
+			{
192
+				case 'length':
193
+					$result[$name] = ($value <= mb_strlen($password));
194
+					break;
195
+					
196
+				case 'digit':
197
+					$result[$name] = ($value <= preg_match_all('/\d/', $password));
198
+					break;
199
+					
200
+				case 'letter':
201
+					$result[$name] = ($value <= preg_match_all('/[a-zA-Z]/', $password));
202
+					break;
203
+					
204
+				case 'lowercase':
205
+					$result[$name] = ($value <= preg_match_all('/[a-z]/', $password));
206
+					break;
207
+					
208
+				case 'uppercase':
209
+					$result[$name] = ($value <= preg_match_all('/[A-Z]/', $password));
210
+					break;
211
+					
212
+				case 'special':
213
+					$result[$name] = ($value <= preg_match_all('/[\W_]/', $password));
214
+					break;
215
+			}
216
+		}
217
+		
218
+		return $result;
219
+	}
220
+	
221
+	/**
222
+	 * Retourne la police de mot de passe sous forme de tableau
223
+	 * de la forme Type de caractère => nb minimal requis.
224
+	 * @return array
225
+	 */
226
+	public static function getPolicy(SettingsManager $manager)
227
+	{
228
+		
229
+		$policyString = $manager->get("password_policy");//$homeConfig->getValue('password_policy');
230
+		$policy = array();
231
+		
232
+		parse_str($policyString, $policy);
233
+		
234
+		return $policy;
235
+	}
236
+}
237
+?>

src/CaptainLearning/Entity/User.php → src/CaptainLearning/Entity/User/User.php View File

@@ -1,5 +1,5 @@
1 1
 <?php
2
-namespace Logipro\CaptainLearning\Entity;
2
+namespace Logipro\CaptainLearning\Entity\User;
3 3
 
4 4
 use Logipro\CaptainLearning\Entity\Common\AbstractEntity;
5 5
 use Symfony\Component\Security\Core\User\UserInterface;

+ 90
- 0
src/CaptainLearning/Event/EventSubscriber/CaptainLearningEventSubscriber.php View File

@@ -0,0 +1,90 @@
1
+<?php
2
+namespace Logipro\CaptainLearning\Event\EventSubscriber;
3
+
4
+use Symfony\Component\Routing\RouterInterface;
5
+use Symfony\Component\HttpFoundation\RequestStack;
6
+use Symfony\Component\EventDispatcher\GenericEvent;
7
+use Symfony\Component\Translation\TranslatorInterface;
8
+use Symfony\Component\DependencyInjection\ContainerInterface;
9
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
10
+
11
+abstract class CaptainLearningEventSubscriber implements EventSubscriberInterface
12
+{
13
+	/**
14
+	 * @var Swift_Mailer
15
+	 */
16
+	protected $mailer;
17
+
18
+	/**
19
+	 * @var ContainerInterface
20
+	 */
21
+	protected $container;
22
+
23
+	/**
24
+	 * @var TranslatorInterface
25
+	 */
26
+	protected $translator;
27
+
28
+	/**
29
+	 * @var RouterInterface
30
+	 */
31
+	protected $router;
32
+
33
+	/**
34
+	 * @var Request
35
+	 */
36
+	protected $request; 
37
+
38
+	protected $sender;
39
+
40
+	public function __construct(\Swift_Mailer $mailer, ContainerInterface $container, TranslatorInterface $translator,RouterInterface $router,RequestStack $request, $sender)
41
+	{
42
+		$this->mailer = $mailer;
43
+		$this->container = $container;
44
+		$this->translator = $translator;
45
+		$this->router = $router;
46
+		$this->request = $request; 
47
+		$this->sender = $sender;
48
+	}
49
+
50
+	/**
51
+	 * créé un objet swif_message a partir des données en entrée
52
+	 *
53
+	 * @param GenericEvent $event
54
+	 * @param string $subjectVariableName
55
+	 * @param string $view chemin de la vue, a partir du dossier template/emails
56
+	 * @param array $parameters
57
+	 *
58
+	 * @return \Swift_Message
59
+	 */
60
+	protected function createSwiftMailerFromEvent(GenericEvent $event,string $subjectVariableName,string $view,Array $parameters = array()) : \Swift_Message
61
+    {
62
+    	// Récupération d'element de config
63
+    	$user = $event->getSubject();
64
+    	$emailTo = $user->getEmail();
65
+
66
+		$currentRequest = $this->request->getCurrentRequest();
67
+
68
+		$emailTo = 'raphael.grand@logipro.com';
69
+    	$default = array(
70
+			'to' => $emailTo,
71
+			'url_captain' => $currentRequest->getSchemeAndHttpHost() . $this->router->generate('index')
72
+    	);
73
+
74
+    	foreach ($parameters as $name => $value)
75
+    	{
76
+    		$default[$name] = $value;
77
+    	}
78
+
79
+    	return (new \Swift_Message())
80
+    	->setSubject('Captain Learning - ' . $this->translator->trans($subjectVariableName))
81
+    	->setFrom($this->sender)
82
+    	->setTo($emailTo)
83
+    	->setBody(
84
+    		$this->container->get('twig')->render('emails/' . $view, $default),
85
+    		'text/html'
86
+    	);
87
+
88
+
89
+    }
90
+}

+ 85
- 0
src/CaptainLearning/Event/EventSubscriber/PasswordRecoverNotifyUser.php View File

@@ -0,0 +1,85 @@
1
+<?php
2
+namespace Logipro\CaptainLearning\Event\EventSubscriber;
3
+
4
+use Logipro\CaptainLearning\Event\Events;
5
+use Symfony\Component\Routing\RouterInterface;
6
+use Symfony\Component\HttpFoundation\RequestStack;
7
+use Symfony\Component\EventDispatcher\GenericEvent;
8
+use Symfony\Component\Translation\TranslatorInterface;
9
+use Symfony\Component\DependencyInjection\ContainerInterface;
10
+use Logipro\CaptainLearning\Event\EventSubscriber\CaptainLearningEventSubscriber;
11
+
12
+use Defuse\Crypto\Crypto;
13
+use Defuse\Crypto\Key;
14
+
15
+/**
16
+ * Envoi un mail "mot de passe publié"
17
+ *
18
+ */
19
+class PasswordRecoverNotifyUser extends CaptainLearningEventSubscriber
20
+{
21
+    public function __construct(\Swift_Mailer $mailer, ContainerInterface $container, TranslatorInterface $translator,RouterInterface $router,RequestStack $request, $sender)
22
+    {
23
+        parent::__construct($mailer,$container,$translator,$router,$request,$sender);
24
+    }
25
+
26
+    /**
27
+     * correspondance des evenements et de la méthode appelée
28
+     *
29
+     * @return array
30
+     */
31
+    public static function getSubscribedEvents(): array
32
+    {
33
+        return [
34
+        	Events::USER_RECOVER_PASSWORD => 'onUserRecoverPassword',
35
+        ];
36
+    }
37
+
38
+
39
+    /**
40
+     * Fonction executé quand un utilisateur demande a redéfinir son mot de passe
41
+     *
42
+     * @param GenericEvent $event
43
+     *
44
+     * @return void
45
+     */
46
+    public function onUserRecoverPassword(GenericEvent $event): void
47
+    {
48
+    	$this->sendEmail($event);
49
+    }
50
+
51
+    /**
52
+     * envoi un email lorsque l'utilisateur demande a redéfinir son mot de passe
53
+     *
54
+     * @param GenericEvent $event
55
+     *
56
+     * @return void
57
+     */
58
+    private function sendEmail(GenericEvent $event) : void
59
+    {
60
+        $user = $event->getSubject();
61
+
62
+        // variables
63
+       // $ascii = Key::createNewRandomKey();
64
+        /*$key = Key::loadFromAsciiSafeString(bin2hex("azertyuiopazertyuiopazertyuiopazertyuiop"));
65
+
66
+       
67
+        $ciphertext = Crypto::encrypt(serialize($data),$key);*/
68
+       
69
+        $data = array('userId' => $user->getUserId());
70
+        $ciphertext = base64_encode(serialize($data));
71
+        $parameters = array
72
+        (
73
+            'token' => $ciphertext
74
+        );
75
+        dump($parameters);
76
+
77
+    	$swiftMailer = $this->createSwiftMailerFromEvent(
78
+    		$event,
79
+    		'mail_recover_subject',
80
+            'PasswordRecover.html.twig',
81
+            $parameters
82
+    	);
83
+    	$this->mailer->send($swiftMailer);
84
+    }
85
+}

+ 69
- 0
src/CaptainLearning/Event/EventSubscriber/UserSuscribeNotifyUser.php View File

@@ -0,0 +1,69 @@
1
+<?php
2
+namespace Logipro\CaptainLearning\Event\EventSubscriber;
3
+
4
+use Logipro\CaptainLearning\Event\Events;
5
+use Symfony\Component\Routing\RouterInterface;
6
+use Symfony\Component\HttpFoundation\RequestStack;
7
+use Symfony\Component\EventDispatcher\GenericEvent;
8
+use Symfony\Component\Translation\TranslatorInterface;
9
+use Symfony\Component\DependencyInjection\ContainerInterface;
10
+use Logipro\CaptainLearning\Event\EventSubscriber\CaptainLearningEventSubscriber;
11
+/**
12
+ * Envoi un mail "mot de passe publié"
13
+ *
14
+ */
15
+class UserSuscribeNotifyUser extends CaptainLearningEventSubscriber
16
+{
17
+	public function __construct(\Swift_Mailer $mailer, ContainerInterface $container, TranslatorInterface $translator,RouterInterface $router,RequestStack $request, $sender)
18
+    {
19
+        parent::__construct($mailer,$container,$translator,$router,$request,$sender);
20
+    }
21
+
22
+    /**
23
+     * correspondance des evenements et de la méthode appelée
24
+     *
25
+     * @return array
26
+     */
27
+    public static function getSubscribedEvents(): array
28
+    {
29
+        return [
30
+        	Events::USER_SUBSCRIBE => 'onUserSubscribe',
31
+        ];
32
+    }
33
+
34
+
35
+    /**
36
+     * Fonction executé quand un utilisateur s'inscrit
37
+     *
38
+     * @param GenericEvent $event
39
+     *
40
+     * @return void
41
+     */
42
+    public function onUserSubscribe(GenericEvent $event): void
43
+    {
44
+    	$this->sendEmail($event);
45
+    }
46
+
47
+    /**
48
+     * envoi un email lorsque l'utilisateur demande a redéfinir son mot de passe
49
+     *
50
+     * @param GenericEvent $event
51
+     *
52
+     * @return void
53
+     */
54
+    private function sendEmail(GenericEvent $event) : void
55
+    {
56
+    	$url = $currentRequest->getSchemeAndHttpHost() . $this->router->generate('subscribe_confirm',array('token' => $token));
57
+
58
+    	$parameters = array('url' => $url);
59
+
60
+    	$swiftMailer = $this->createSwiftMailerFromEvent(
61
+    		$event,
62
+    		'mail_subscribe_subject',
63
+    		'UserSubscribe.html.twig',
64
+    		$parameters
65
+    	);
66
+
67
+    	$this->mailer->send($swiftMailer);
68
+    }
69
+}

+ 23
- 0
src/CaptainLearning/Event/Events.php View File

@@ -0,0 +1,23 @@
1
+<?php
2
+namespace Logipro\CaptainLearning\Event;
3
+
4
+/**
5
+  * Cette classe définit les noms de tous les événements distribués dans
6
+  * notre projet. Il n'est pas obligatoire de créer un
7
+  * classe comme ça, mais c'est considéré comme une bonne pratique.
8
+  *
9
+  */
10
+final class Events
11
+{
12
+	/**
13
+	 * For the event naming conventions, see:
14
+	 * https://symfony.com/doc/current/components/event_dispatcher.html#naming-conventions.
15
+	 *
16
+	 * @Event("Symfony\Component\EventDispatcher\GenericEvent")
17
+	 *
18
+	 * @var string
19
+	 */
20
+	const USER_RECOVER_PASSWORD = 'user.recovered';
21
+
22
+	const USER_SUBSCRIBE = 'user.subscribe';
23
+}

+ 1
- 0
src/CaptainLearning/Kernel/AppKernel.php View File

@@ -23,6 +23,7 @@ class AppKernel extends Kernel
23 23
 		$bundles[] = new \Symfony\Bundle\MonologBundle\MonologBundle();
24 24
 		$bundles[] = new \Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle();
25 25
 		$bundles[] = new \Vich\UploaderBundle\VichUploaderBundle();
26
+		//$bundles[] = new \Defuse\Crypto\Crypto();
26 27
 		
27 28
 		// Bundles pour le dév uniquement
28 29
 		if ($this->getEnvironment() === 'dev')

+ 1
- 1
src/CaptainLearning/Repository/UserRepository.php View File

@@ -3,7 +3,7 @@ namespace Logipro\CaptainLearning\Repository;
3 3
 
4 4
 use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
5 5
 use Symfony\Bridge\Doctrine\RegistryInterface;
6
-use Logipro\CaptainLearning\Entity\User;
6
+use Logipro\CaptainLearning\Entity\User\User;
7 7
 
8 8
 class UserRepository extends ServiceEntityRepository
9 9
 {

+ 0
- 39
src/CaptainLearning/Security/UserChecker.php View File

@@ -1,39 +0,0 @@
1
-<?php
2
-namespace Logipro\CaptainLearning\Security;
3
-
4
-use Logipro\CaptainLearning\Entity\Type\EnumStatusType;
5
-use Symfony\Component\Security\Core\Exception\DisabledException;
6
-use Symfony\Component\Security\Core\User\UserCheckerInterface;
7
-use Symfony\Component\Security\Core\User\UserInterface;
8
-
9
-class UserChecker implements UserCheckerInterface
10
-{
11
-	/**
12
-	 * Vérifie le compte d'utilisateur avant l'authentification.
13
-	 * {@inheritDoc}
14
-	 * @see \Symfony\Component\Security\Core\User\UserCheckerInterface::checkPreAuth()
15
-	 */
16
-	public function checkPreAuth(UserInterface $user)
17
-	{
18
-		if (!$user instanceof Account) {
19
-			return;
20
-		}
21
-	}
22
-
23
-	/**
24
-	 * Vérifie le compte d'utilisateur après l'authentification.
25
-	 * {@inheritDoc}
26
-	 * @see \Symfony\Component\Security\Core\User\UserCheckerInterface::checkPostAuth()
27
-	 */
28
-	public function checkPostAuth(UserInterface $user)
29
-	{
30
-		if (!$user instanceof Account) {
31
-			return;
32
-		}
33
-
34
-		// compte utilisateur "DISABLED" affichage d'un message d'erreur
35
-		if ($user->getStatus() != EnumAccountStatusType::STATUS_ENABLED) {
36
-			throw new DisabledException('error_login_disabled');
37
-		}
38
-	}
39
-}

+ 3
- 2
templates/common/layout/header/modal/partial/recover.html.twig View File

@@ -1,6 +1,6 @@
1 1
 <div id="bloc-login-recover" class="col col-12 col-lg-6 bg-white d-none p-2 p-lg-4">
2 2
 	<div class="d-flex align-items-center h-100">
3
-		<form id="form-recover" class="login-form p-0 form-with-validation needs-validation w-100 no-autocomplete" action="{{ path('recover') }}" method="post" novalidate>
3
+		<form id="form-recover" class="login-form p-0 form-with-validation needs-validation w-100 no-autocomplete" action="{{ path('recoverPassword') }}" method="post" novalidate>
4 4
 			<input type="hidden" name="action" value="recover" />
5 5
 			<div class="text-center">
6 6
 				<h3 class="text-center my-3">{{ 'label_recover_title'|trans }}</h3>
@@ -18,9 +18,10 @@
18 18
 			</div>
19 19
 
20 20
 			<div class="form-group text-center">
21
-				{{ 'label_recover_btn_submit'|trans }}
21
+				<button class="btn btn-primary" type="submit">{{ 'label_recover_btn_submit'|trans }}</button>
22 22
 			</div>
23 23
 			<div class="text-center">
24
+			
24 25
 				<a href="#" id="link-return-login">{{ 'label_recover_login'|trans }}</a>
25 26
 			</div>
26 27
 		</form>

+ 30
- 0
templates/common/setpassword.html.twig View File

@@ -0,0 +1,30 @@
1
+{% extends "frontOffice/layout/layout.base.html.twig" %}
2
+{% block body %}
3
+	<div class="wrapper-login">
4
+		<div class="login">
5
+			<header class="login-header text-center">
6
+				<a class="login-brand" href="{{ path('index') }}">
7
+					<img src="{{ asset('images/logo-captain-complet.svg') }}" alt="">
8
+				</a>
9
+			</header>
10
+
11
+			<form class="login-form p-3 form-with-validation custom-send needs-validation" action="{{ path('definePassword') }}" method="post" novalidate>
12
+				<p class="form-text text-muted">{{ 'password_policity'|trans }}</p>
13
+				<input type="hidden" name="action" value="setpassword"/>
14
+				<input type="hidden" name="token" value="{{ token }}"/>
15
+				<div class="form-group">
16
+					<label for="motdepasse">{{ 'label_password'|trans }}*</label>
17
+					<input type="password" id="password" name="password" class="form-control" required>
18
+				</div>
19
+				<div class="form-group">
20
+					<label for="motdepasse_copie">{{ 'label_password_copy'|trans }}*</label>
21
+					<input type="password" id="password_copy" name="password_copy" class="form-control" required>
22
+				</div>
23
+				<div class="form-group text-center">
24
+					<button class="btn btn-primary" type="submit">{{ 'label_recover_password_validate'|trans }}</button>
25
+				</div>
26
+			</form>
27
+		</div>
28
+
29
+	</div>
30
+{% endblock %}

+ 16
- 0
templates/emails/PasswordRecover.html.twig View File

@@ -0,0 +1,16 @@
1
+{% extends "emails/layout/layout.base.html.twig" %}
2
+
3
+{% block body %}
4
+ <h1 class="text-center">
5
+ 	Redéfinition de mot de passe
6
+ </h1>
7
+ <p>
8
+ 	<strong>Vous avez oublié votre mot de passe ?</strong> Pas de problème : nous pouvons vous aider ! 
9
+ </p>
10
+ <p>
11
+ 	Il vous suffit de cliquer sur le lien ci-dessous qui vous permettra de le réinitialiser.
12
+ </p>
13
+ <p>
14
+ 	<a href="{{ recover_url }}">Redéfinir mon mot de passe</a>
15
+ </p>
16
+{% endblock %}

+ 9
- 0
templates/emails/UserSubscribe.html.twig View File

@@ -0,0 +1,9 @@
1
+{% extends "emails/layout/layout.base.html.twig" %}
2
+
3
+{% block body %}
4
+Bonjour,<br/>
5
+Vous venez de créer votre compte utilisateur pour accéder à votre tableau de bord LAMA et nous vous en remercions.
6
+Encore une étape avant de finaliser votre inscription, il ne vous reste plus qu'à suivre le lien ci-dessous : <br/>
7
+<a href="{{ url }}">M'inscrire sur LAMA</a><br/><br/>
8
+Une fois votre adresse validée vous pourrez bénéficier des services de LAMA !
9
+{% endblock %}

+ 1458
- 0
templates/emails/layout/layout.base.html.twig
File diff suppressed because it is too large
View File


+ 69
- 1
translations/messages.fr.xlf View File

@@ -15,6 +15,10 @@
15 15
 				<source>label_setting_elasticsearch_captain_learning_index_name</source>
16 16
 				<target>Il s'agit de l'identifiant de l'index (équivalent d'un schéma de base de données) de Captain Learning sur Elasticsearch</target>
17 17
 			</trans-unit>
18
+			<trans-unit id="label_setting_password_policy_name">
19
+				<source>label_setting_password_policy_name</source>
20
+				<target>Politique de sécurité du mot de passe</target>
21
+			</trans-unit>
18 22
 			<!-- fin configurations -->
19 23
 
20 24
 			<!-- enum -->
@@ -311,7 +315,71 @@
311 315
 				<target>Lieux</target>
312 316
 			</trans-unit>
313 317
 			<!-- fin back office Vendeur -->
314
-			
318
+
319
+
320
+			<!-- Politique de mot de passe -->
321
+			<trans-unit id="label_password_mask_has_caracters">
322
+				<source>label_password_mask_has_caracters</source>
323
+				<target>%s caractère(s)</target>
324
+			</trans-unit>
325
+			<trans-unit id="label_password_mask_has_numbers">
326
+				<source>label_password_mask_has_numbers</source>
327
+				<target>%s chiffre(s)</target>
328
+			</trans-unit>
329
+			<trans-unit id="label_password_mask_has_letters">
330
+				<source>label_password_mask_has_letters</source>
331
+				<target>%s lettre(s)</target>
332
+			</trans-unit>
333
+			<trans-unit id="label_password_has_lowercases">
334
+				<source>label_password_has_lowercases</source>
335
+				<target>%s lettre(s) minuscule</target>
336
+			</trans-unit>
337
+			<trans-unit id="label_password_has_uppercases">
338
+				<source>label_password_has_uppercases</source>
339
+				<target>%s lettre(s) majuscules</target>
340
+			</trans-unit>
341
+			<trans-unit id="label_password_has_specials">
342
+				<source>label_password_has_special</source>
343
+				<target>%s caractère(s) spéciaux</target>
344
+			</trans-unit>
345
+			<trans-unit id="label_password_has_caracter">
346
+				<source>label_password_has_caracter</source>
347
+				<target>un caractère</target>
348
+			</trans-unit>
349
+			<trans-unit id="label_password_has_number">
350
+				<source>label_password_has_number</source>
351
+				<target>un chiffre</target>
352
+			</trans-unit>
353
+			<trans-unit id="label_password_has_letter">
354
+				<source>label_password_has_letter</source>
355
+				<target>une lettre</target>
356
+			</trans-unit>
357
+			<trans-unit id="label_password_has_lowercase">
358
+				<source>label_password_has_lowercase</source>
359
+				<target>une lettre minuscule</target>
360
+			</trans-unit>
361
+			<trans-unit id="label_password_uppercase">
362
+				<source>label_password_uppercase</source>
363
+				<target>une lettre majuscule</target>
364
+			</trans-unit>
365
+			<trans-unit id="label_password_special">
366
+				<source>label_password_special</source>
367
+				<target>un caractère spécial</target>
368
+			</trans-unit>
369
+
370
+
371
+			<trans-unit id="label_password_error_mask">
372
+				<source>label_password_error_mask</source>
373
+				<target>Le mot de passe doit avoir au minimum </target>
374
+			</trans-unit>
375
+			<trans-unit id="label_password_error_mask_link">
376
+				<source>label_password_error_mask_link</source>
377
+				<target> et %s</target>
378
+			</trans-unit>
379
+			<trans-unit id="label_password_error_mask_with">
380
+				<source>label_password_error_mask_with</source>
381
+				<target>contenant au minimum </target>
382
+			</trans-unit>
315 383
 		</body>
316 384
 	</file>
317 385
 </xliff>

Loading…
Cancel
Save