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 ;
1312use Drupal \user \Entity \User ;
13+ use Drupal \user \UserInterface ;
1414use Drupal \user \UserStorageInterface ;
1515use Firebase \JWT \JWT ;
1616use Firebase \JWT \Key ;
1919use Psr \Log \LogLevel ;
2020use Symfony \Component \DependencyInjection \Attribute \Autowire ;
2121use Symfony \Component \HttpFoundation \Exception \BadRequestException ;
22- use Symfony \Component \HttpFoundation \JsonResponse ;
2322use Symfony \Component \HttpFoundation \Request ;
2423use Symfony \Component \HttpFoundation \Response ;
2524use Symfony \Component \HttpKernel \Exception \BadRequestHttpException ;
@@ -86,17 +85,7 @@ public function start(Request $request, ?string $jwt): Response {
8685 throw new BadRequestHttpException ('Missing or empty JWT ' );
8786 }
8887
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 ;
88+ $ payload = $ this ->decodeJwt ($ jwt );
10089
10190 $ this ->debug ('@debug ' , [
10291 '@debug ' => json_encode ([
@@ -109,51 +98,36 @@ public function start(Request $request, ?string $jwt): Response {
10998 throw new BadRequestHttpException ('Missing username ' );
11099 }
111100
112- $ user = $ this ->loadUser ($ username );
101+ // Check that we can get userinfo.
102+ $ userinfo = $ this ->fetchUserinfo ($ username );
113103
114104 $ this ->debug ('@debug ' , [
115105 '@debug ' => json_encode ([
116- 'user ' => $ user ,
106+ 'userinfo ' => $ userinfo ,
117107 ]),
118108 ]);
119109
120- if (empty ($ user )) {
110+ if (empty ($ userinfo )) {
121111 // Don't disclose whether or not the user exists.
122112 throw new BadRequestHttpException ();
123113 }
124114
125- // Check that we can get userinfo.
126- $ userinfo = $ this ->getUserinfo ($ user );
115+ $ user = $ this ->ensureUser ($ username , $ userinfo );
127116
128117 $ this ->debug ('@debug ' , [
129118 '@debug ' => json_encode ([
130- 'userinfo ' => $ userinfo ,
119+ 'user ' => $ user ,
131120 ]),
132121 ]);
133122
134- if (empty ($ userinfo )) {
123+ if (empty ($ user )) {
124+ // Don't disclose whether or not the user exists.
135125 throw new BadRequestHttpException ();
136126 }
137127
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- ]);
128+ return Request::METHOD_POST === $ request ->getMethod ()
129+ ? $ this ->createAuthenticateResponse ($ user )
130+ : $ this ->authenticateUser ($ user );
157131 }
158132 catch (\Exception $ exception ) {
159133 $ this ->error ('start: @message ' , ['@message ' => $ exception ->getMessage (), $ exception ]);
@@ -162,17 +136,37 @@ public function start(Request $request, ?string $jwt): Response {
162136 }
163137
164138 /**
165- * Authenticate user.
139+ * Create authenticate response.
140+ */
141+ private function createAuthenticateResponse (UserInterface $ user ): Response {
142+ // https://github.com/firebase/php-jwt?tab=readme-ov-file#example
143+ $ payload = [
144+ // Issued at.
145+ 'iat ' => $ this ->time ->getRequestTime (),
146+ // Expire af 60 seconds.
147+ 'exp ' => $ this ->time ->getRequestTime () + 60 ,
148+ 'username ' => $ user ->getAccountName (),
149+ ];
150+ $ jwt = $ this ->encodeJwt ($ payload );
151+
152+ $ url = Url::fromRoute ('os2loop_cura_login.authenticate ' , [
153+ 'jwt ' => $ jwt ,
154+ ])->setAbsolute ()->toString (TRUE )->getGeneratedUrl ();
155+
156+ return new Response ($ url );
157+ }
158+
159+ /**
160+ * Authenticate action.
166161 */
167162 public function authenticate (Request $ request ): Response {
168163 try {
169- $ username = $ request ->get ('username ' );
170164 $ jwt = $ request ->get ('jwt ' );
171- if (empty ($ username ) || empty ( $ jwt )) {
165+ if (empty ($ jwt )) {
172166 throw new BadRequestHttpException ();
173167 }
174168
175- $ payload = ( array ) JWT :: decode ( $ jwt, new Key ( self :: JWT_KEY , ' HS256 ' ) );
169+ $ payload = $ this -> decodeJwt ( $ jwt );
176170 $ username = $ payload ['username ' ] ?? NULL ;
177171 if (empty ($ username )) {
178172 throw new BadRequestHttpException ();
@@ -184,54 +178,124 @@ public function authenticate(Request $request): Response {
184178 throw new BadRequestHttpException ();
185179 }
186180
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 );
181+ return $ this ->authenticateUser ($ user );
195182 }
196183 catch (\Exception $ exception ) {
197- $ this ->error ('start : @message ' , ['@message ' => $ exception ->getMessage (), $ exception ]);
184+ $ this ->error ('authenticate : @message ' , ['@message ' => $ exception ->getMessage (), $ exception ]);
198185 throw new BadRequestException ($ exception ->getMessage ());
199186 }
200187 }
201188
202189 /**
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.
190+ * Authenticate user.
210191 */
211- private function loadUser ( string $ username ): ? User {
212- $ users = $ this -> userStorage -> loadByProperties ([ ' name ' => $ username ] );
192+ private function authenticateUser ( $ user ): Response {
193+ user_login_finalize ( $ user );
213194
214- return reset ($ users ) ?: NULL ;
195+ $ this ->messenger ()->addStatus ($ this ->t ('Welcome Cura user @user. ' , ['@user ' => $ user ->getDisplayName ()]));
196+
197+ return $ this ->redirect ('<front> ' );
215198 }
216199
217200 /**
218- * Update user with info from IdP .
201+ * Encode JWT .
219202 */
220- private function updateUser (User $ user ): User {
221- // $userinfo = $this->getUserinfo($user);
222- // @todo Update user.
223- return $ user ;
203+ private function encodeJwt (array $ payload ): string {
204+ $ secret = $ this ->settings ->getSigningSecret ();
205+ // @todo Get rid of the double base64 encoding.
206+ $ secret = base64_decode ($ secret );
207+
208+ return JWT ::encode ($ payload , $ secret , $ this ->settings ->getSigningAlgorithm ());
209+ }
210+
211+ /**
212+ * Decode JWT.
213+ */
214+ private function decodeJwt (string $ jwt ): array {
215+ $ secret = $ this ->settings ->getSigningSecret ();
216+ // @todo Get rid of the double base64 encoding.
217+ $ secret = base64_decode ($ secret );
218+
219+ $ originalLeeway = JWT ::$ leeway ;
220+ $ leeway = $ this ->settings ->getJwtLeeway ();
221+ if ($ leeway > 0 ) {
222+ JWT ::$ leeway = $ leeway ;
223+ }
224+ $ payload = (array ) JWT ::decode ($ jwt , new Key ($ secret , $ this ->settings ->getSigningAlgorithm ()));
225+ JWT ::$ leeway = $ originalLeeway ;
226+
227+ return $ payload ;
224228 }
225229
226230 /**
227231 * Get user info from userinfo endpoint.
228232 */
229- private function getUserinfo ( User $ user ): array {
233+ private function fetchUserinfo ( string $ username ): array {
230234 return [
231- 'name ' => $ user ->getDisplayName (),
235+ // Drupal user fields.
236+ 'name ' => $ username ,
237+ 'mail ' => $ username . '@cura.example.com ' ,
238+
239+ // OS2Lloop fields
240+ // 'os2loop_user_address' => '',
241+ // 'os2loop_user_areas_of_expertise' => '',
242+ // 'os2loop_user_biography' => '',
243+ // 'os2loop_user_city' => '',
244+ // 'os2loop_user_external_list' => '',.
245+ 'os2loop_user_family_name ' => 'Cura ' ,
246+ 'os2loop_user_given_name ' => 'User ' ,
247+ // 'os2loop_user_image' => '',
248+ // 'os2loop_user_internal_list' => '',
249+ // 'os2loop_user_job_title' => '',
250+ // 'os2loop_user_phone_number' => '',
251+ // 'os2loop_user_place' => '',
252+ // 'os2loop_user_postal_code' => '',
253+ // 'os2loop_user_professions' => '',
232254 ];
233255 }
234256
257+ /**
258+ * Ensure user exists.
259+ *
260+ * @param string $username
261+ * The username.
262+ * @param array $userinfo
263+ * The user info to set on the user.
264+ *
265+ * @return \Drupal\user\Entity\UserInterface
266+ * The newly created or updated user.
267+ */
268+ private function ensureUser (string $ username , array $ userinfo ): UserInterface {
269+ $ user = $ this ->loadUser ($ username );
270+
271+ if (NULL === $ user ) {
272+ $ user = $ this ->userStorage ->create ();
273+ }
274+
275+ foreach ($ userinfo as $ field => $ value ) {
276+ $ currentValue = $ user ->get ($ field );
277+ if ($ currentValue !== $ value ) {
278+ $ user ->set ($ field , $ value );
279+ }
280+ }
281+
282+ // Make sure that the user is active.
283+ $ user
284+ ->activate ()
285+ ->save ();
286+
287+ return $ user ;
288+ }
289+
290+ /**
291+ * Load user by username.
292+ */
293+ private function loadUser (string $ username ) : ?UserInterface {
294+ $ users = $ this ->userStorage ->loadByProperties (['name ' => $ username ]);
295+
296+ return reset ($ users ) ?: NULL ;
297+ }
298+
235299 /**
236300 * {@inheritdoc}
237301 */
0 commit comments