77use Drupal \Component \Datetime \TimeInterface ;
88use Drupal \Core \Controller \ControllerBase ;
99use Drupal \Core \Logger \RfcLogLevel ;
10- use Drupal \Core \Routing \TrustedRedirectResponse ;
1110use Drupal \Core \Url ;
1211use Drupal \os2loop_cura_login \Settings ;
13- use Drupal \user \Entity \ User ;
12+ use Drupal \user \UserInterface ;
1413use Drupal \user \UserStorageInterface ;
1514use Firebase \JWT \JWT ;
1615use Firebase \JWT \Key ;
1918use Psr \Log \LogLevel ;
2019use Symfony \Component \DependencyInjection \Attribute \Autowire ;
2120use Symfony \Component \HttpFoundation \Exception \BadRequestException ;
22- use Symfony \Component \HttpFoundation \JsonResponse ;
2321use Symfony \Component \HttpFoundation \Request ;
2422use Symfony \Component \HttpFoundation \Response ;
2523use Symfony \Component \HttpKernel \Exception \BadRequestHttpException ;
@@ -86,17 +84,7 @@ public function start(Request $request, ?string $jwt): Response {
8684 throw new BadRequestHttpException ('Missing or empty JWT ' );
8785 }
8886
89- $ secret = $ this ->settings ->getSigningSecret ();
90- // @todo Get rid of the double base64 encoding.
91- $ secret = base64_decode ($ secret );
92-
93- $ originalLeeway = JWT ::$ leeway ;
94- $ leeway = $ this ->settings ->getJwtLeeway ();
95- if ($ leeway > 0 ) {
96- JWT ::$ leeway = $ leeway ;
97- }
98- $ payload = (array ) JWT ::decode ($ jwt , new Key ($ secret , $ this ->settings ->getSigningAlgorithm ()));
99- JWT ::$ leeway = $ originalLeeway ;
87+ $ payload = $ this ->decodeJwt ($ jwt );
10088
10189 $ this ->debug ('@debug ' , [
10290 '@debug ' => json_encode ([
@@ -109,51 +97,36 @@ public function start(Request $request, ?string $jwt): Response {
10997 throw new BadRequestHttpException ('Missing username ' );
11098 }
11199
112- $ user = $ this ->loadUser ($ username );
100+ // Check that we can get userinfo.
101+ $ userinfo = $ this ->fetchUserinfo ($ username );
113102
114103 $ this ->debug ('@debug ' , [
115104 '@debug ' => json_encode ([
116- 'user ' => $ user ,
105+ 'userinfo ' => $ userinfo ,
117106 ]),
118107 ]);
119108
120- if (empty ($ user )) {
109+ if (empty ($ userinfo )) {
121110 // Don't disclose whether or not the user exists.
122111 throw new BadRequestHttpException ();
123112 }
124113
125- // Check that we can get userinfo.
126- $ userinfo = $ this ->getUserinfo ($ user );
114+ $ user = $ this ->ensureUser ($ username , $ userinfo );
127115
128116 $ this ->debug ('@debug ' , [
129117 '@debug ' => json_encode ([
130- 'userinfo ' => $ userinfo ,
118+ 'user ' => $ user ,
131119 ]),
132120 ]);
133121
134- if (empty ($ userinfo )) {
122+ if (empty ($ user )) {
123+ // Don't disclose whether or not the user exists.
135124 throw new BadRequestHttpException ();
136125 }
137126
138- // https://github.com/firebase/php-jwt?tab=readme-ov-file#example
139- $ payload = [
140- // Issued at.
141- 'iat ' => $ this ->time ->getRequestTime (),
142- // Expire af 60 seconds.
143- 'exp ' => $ this ->time ->getRequestTime () + 60 ,
144- 'username ' => $ username ,
145- ];
146- $ jwt = JWT ::encode ($ payload , self ::JWT_KEY , 'HS256 ' );
147-
148- $ url = Url::fromRoute ('os2loop_cura_login.authenticate ' , [
149- 'username ' => $ username ,
150- 'jwt ' => $ jwt ,
151- ])->setAbsolute ()->toString (TRUE )->getGeneratedUrl ();
152-
153- return new JsonResponse ([
154- 'authenticate_url ' => $ url ,
155- 'jwt ' => $ jwt ,
156- ]);
127+ return Request::METHOD_POST === $ request ->getMethod ()
128+ ? $ this ->createAuthenticateResponse ($ user )
129+ : $ this ->authenticateUser ($ user );
157130 }
158131 catch (\Exception $ exception ) {
159132 $ this ->error ('start: @message ' , ['@message ' => $ exception ->getMessage (), $ exception ]);
@@ -162,17 +135,37 @@ public function start(Request $request, ?string $jwt): Response {
162135 }
163136
164137 /**
165- * Authenticate user.
138+ * Create authenticate response.
139+ */
140+ private function createAuthenticateResponse (UserInterface $ user ): Response {
141+ // https://github.com/firebase/php-jwt?tab=readme-ov-file#example
142+ $ payload = [
143+ // Issued at.
144+ 'iat ' => $ this ->time ->getRequestTime (),
145+ // Expire af 60 seconds.
146+ 'exp ' => $ this ->time ->getRequestTime () + 60 ,
147+ 'username ' => $ user ->getAccountName (),
148+ ];
149+ $ jwt = $ this ->encodeJwt ($ payload );
150+
151+ $ url = Url::fromRoute ('os2loop_cura_login.authenticate ' , [
152+ 'jwt ' => $ jwt ,
153+ ])->setAbsolute ()->toString (TRUE )->getGeneratedUrl ();
154+
155+ return new Response ($ url );
156+ }
157+
158+ /**
159+ * Authenticate action.
166160 */
167161 public function authenticate (Request $ request ): Response {
168162 try {
169- $ username = $ request ->get ('username ' );
170163 $ jwt = $ request ->get ('jwt ' );
171- if (empty ($ username ) || empty ( $ jwt )) {
164+ if (empty ($ jwt )) {
172165 throw new BadRequestHttpException ();
173166 }
174167
175- $ payload = ( array ) JWT :: decode ( $ jwt, new Key ( self :: JWT_KEY , ' HS256 ' ) );
168+ $ payload = $ this -> decodeJwt ( $ jwt );
176169 $ username = $ payload ['username ' ] ?? NULL ;
177170 if (empty ($ username )) {
178171 throw new BadRequestHttpException ();
@@ -184,54 +177,124 @@ public function authenticate(Request $request): Response {
184177 throw new BadRequestHttpException ();
185178 }
186179
187- $ this ->updateUser ($ user );
188-
189- user_login_finalize ($ user );
190-
191- $ url = Url::fromRoute ('<front> ' )->setAbsolute ()->toString (TRUE )->getGeneratedUrl ();
192- $ this ->messenger ()->addStatus ($ this ->t ('Welcome @user. ' , ['@user ' => $ user ->getDisplayName ()]));
193-
194- return new TrustedRedirectResponse ($ url );
180+ return $ this ->authenticateUser ($ user );
195181 }
196182 catch (\Exception $ exception ) {
197- $ this ->error ('start : @message ' , ['@message ' => $ exception ->getMessage (), $ exception ]);
183+ $ this ->error ('authenticate : @message ' , ['@message ' => $ exception ->getMessage (), $ exception ]);
198184 throw new BadRequestException ($ exception ->getMessage ());
199185 }
200186 }
201187
202188 /**
203- * Load user by username.
204- *
205- * @param string $username
206- * The username.
207- *
208- * @return \Drupal\user\Entity\User|null
209- * The user if any.
189+ * Authenticate user.
210190 */
211- private function loadUser ( string $ username ): ? User {
212- $ users = $ this -> userStorage -> loadByProperties ([ ' name ' => $ username ] );
191+ private function authenticateUser ( $ user ): Response {
192+ user_login_finalize ( $ user );
213193
214- return reset ($ users ) ?: NULL ;
194+ $ this ->messenger ()->addStatus ($ this ->t ('Welcome Cura user @user. ' , ['@user ' => $ user ->getDisplayName ()]));
195+
196+ return $ this ->redirect ('<front> ' );
215197 }
216198
217199 /**
218- * Update user with info from IdP .
200+ * Encode JWT .
219201 */
220- private function updateUser (User $ user ): User {
221- // $userinfo = $this->getUserinfo($user);
222- // @todo Update user.
223- return $ user ;
202+ private function encodeJwt (array $ payload ): string {
203+ $ secret = $ this ->settings ->getSigningSecret ();
204+ // @todo Get rid of the double base64 encoding.
205+ $ secret = base64_decode ($ secret );
206+
207+ return JWT ::encode ($ payload , $ secret , $ this ->settings ->getSigningAlgorithm ());
208+ }
209+
210+ /**
211+ * Decode JWT.
212+ */
213+ private function decodeJwt (string $ jwt ): array {
214+ $ secret = $ this ->settings ->getSigningSecret ();
215+ // @todo Get rid of the double base64 encoding.
216+ $ secret = base64_decode ($ secret );
217+
218+ $ originalLeeway = JWT ::$ leeway ;
219+ $ leeway = $ this ->settings ->getJwtLeeway ();
220+ if ($ leeway > 0 ) {
221+ JWT ::$ leeway = $ leeway ;
222+ }
223+ $ payload = (array ) JWT ::decode ($ jwt , new Key ($ secret , $ this ->settings ->getSigningAlgorithm ()));
224+ JWT ::$ leeway = $ originalLeeway ;
225+
226+ return $ payload ;
224227 }
225228
226229 /**
227230 * Get user info from userinfo endpoint.
228231 */
229- private function getUserinfo ( User $ user ): array {
232+ private function fetchUserinfo ( string $ username ): array {
230233 return [
231- 'name ' => $ user ->getDisplayName (),
234+ // Drupal user fields.
235+ 'name ' => $ username ,
236+ 'mail ' => $ username . '@cura.example.com ' ,
237+
238+ // OS2Lloop fields
239+ // 'os2loop_user_address' => '',
240+ // 'os2loop_user_areas_of_expertise' => '',
241+ // 'os2loop_user_biography' => '',
242+ // 'os2loop_user_city' => '',
243+ // 'os2loop_user_external_list' => '',.
244+ 'os2loop_user_family_name ' => 'Cura ' ,
245+ 'os2loop_user_given_name ' => 'User ' ,
246+ // 'os2loop_user_image' => '',
247+ // 'os2loop_user_internal_list' => '',
248+ // 'os2loop_user_job_title' => '',
249+ // 'os2loop_user_phone_number' => '',
250+ // 'os2loop_user_place' => '',
251+ // 'os2loop_user_postal_code' => '',
252+ // 'os2loop_user_professions' => '',
232253 ];
233254 }
234255
256+ /**
257+ * Ensure user exists.
258+ *
259+ * @param string $username
260+ * The username.
261+ * @param array $userinfo
262+ * The user info to set on the user.
263+ *
264+ * @return \Drupal\user\Entity\UserInterface
265+ * The newly created or updated user.
266+ */
267+ private function ensureUser (string $ username , array $ userinfo ): UserInterface {
268+ $ user = $ this ->loadUser ($ username );
269+
270+ if (NULL === $ user ) {
271+ $ user = $ this ->userStorage ->create ();
272+ }
273+
274+ foreach ($ userinfo as $ field => $ value ) {
275+ $ currentValue = $ user ->get ($ field );
276+ if ($ currentValue !== $ value ) {
277+ $ user ->set ($ field , $ value );
278+ }
279+ }
280+
281+ // Make sure that the user is active.
282+ $ user
283+ ->activate ()
284+ ->save ();
285+
286+ return $ user ;
287+ }
288+
289+ /**
290+ * Load user by username.
291+ */
292+ private function loadUser (string $ username ) : ?UserInterface {
293+ $ users = $ this ->userStorage ->loadByProperties (['name ' => $ username ]);
294+
295+ return reset ($ users ) ?: NULL ;
296+ }
297+
235298 /**
236299 * {@inheritdoc}
237300 */
0 commit comments