11package me .kavin .piped .server .handlers .auth ;
22
33import com .fasterxml .jackson .core .JsonProcessingException ;
4+ import com .nimbusds .jose .JOSEException ;
5+ import com .nimbusds .jose .proc .BadJOSEException ;
6+ import com .nimbusds .jwt .JWT ;
47import com .nimbusds .jwt .JWTClaimsSet ;
8+ import com .nimbusds .jwt .JWTParser ;
59import com .nimbusds .oauth2 .sdk .*;
610import com .nimbusds .oauth2 .sdk .auth .ClientAuthentication ;
711import com .nimbusds .oauth2 .sdk .auth .ClientSecretBasic ;
812import com .nimbusds .oauth2 .sdk .id .State ;
13+ import com .nimbusds .oauth2 .sdk .pkce .CodeChallengeMethod ;
14+ import com .nimbusds .oauth2 .sdk .pkce .CodeVerifier ;
915import com .nimbusds .openid .connect .sdk .*;
16+ import com .nimbusds .openid .connect .sdk .claims .IDTokenClaimsSet ;
1017import com .nimbusds .openid .connect .sdk .claims .UserInfo ;
1118import io .activej .http .HttpResponse ;
1219import jakarta .persistence .criteria .CriteriaBuilder ;
@@ -131,16 +138,20 @@ public static HttpResponse oidcLoginResponse(OidcProvider provider, String redir
131138 }
132139
133140 URI callback = new URI (Constants .PUBLIC_URL + "/oidc/" + provider .name + "/callback" );
134- OidcData data = new OidcData (redirectUri );
141+ CodeVerifier codeVerifier = new CodeVerifier ();
142+ OidcData data = new OidcData (redirectUri , codeVerifier );
135143 String state = data .getState ();
136144
137145 PENDING_OIDC .put (state , data );
138146
139147 AuthenticationRequest oidcRequest = new AuthenticationRequest .Builder (
140148 new ResponseType ("code" ),
141149 new Scope ("openid" ),
142- provider .clientID , callback ).endpointURI (provider .authUri )
143- .state (new State (state )).nonce (data .nonce ).build ();
150+ provider .clientID , callback )
151+ .endpointURI (provider .authUri )
152+ .codeChallenge (codeVerifier , CodeChallengeMethod .S256 )
153+ .state (new State (state ))
154+ .nonce (data .nonce ).build ();
144155
145156 if (redirectUri .equals (Constants .FRONTEND_URL + "/login" )) {
146157 return HttpResponse .redirect302 (oidcRequest .toURI ().toString ());
@@ -155,24 +166,25 @@ public static HttpResponse oidcLoginResponse(OidcProvider provider, String redir
155166 "\" >here</a></body></html>" );
156167 }
157168 public static HttpResponse oidcCallbackResponse (OidcProvider provider , URI requestUri ) throws Exception {
158- ClientAuthentication clientAuth = new ClientSecretBasic (provider .clientID , provider .clientSecret );
159-
160- AuthenticationSuccessResponse sr = parseOidcUri (requestUri );
169+ AuthenticationSuccessResponse authResponse = parseOidcUri (requestUri );
161170
162- OidcData data = PENDING_OIDC .get (sr .getState ().toString ());
171+ OidcData data = PENDING_OIDC .get (authResponse .getState ().toString ());
163172 if (data == null ) {
164173 return HttpResponse .ofCode (400 ).withHtml (
165174 "Your oidc provider sent invalid state data. Try again or contact your oidc admin"
166175 );
167176 }
168177
169178 URI callback = new URI (Constants .PUBLIC_URL + "/oidc/" + provider .name + "/callback" );
170- AuthorizationCode code = sr .getAuthorizationCode ();
171- AuthorizationGrant codeGrant = new AuthorizationCodeGrant (code , callback );
179+ AuthorizationCode code = authResponse .getAuthorizationCode ();
172180
181+ AuthorizationGrant codeGrant = new AuthorizationCodeGrant (code , callback , data .pkceVerifier );
173182
183+ ClientAuthentication clientAuth = new ClientSecretBasic (provider .clientID , provider .clientSecret );
174184 TokenRequest tokenReq = new TokenRequest (provider .tokenUri , clientAuth , codeGrant );
175- OIDCTokenResponse tokenResponse = (OIDCTokenResponse ) OIDCTokenResponseParser .parse (tokenReq .toHTTPRequest ().send ());
185+
186+ com .nimbusds .oauth2 .sdk .http .HTTPResponse tokenResponseText = tokenReq .toHTTPRequest ().send ();
187+ OIDCTokenResponse tokenResponse = (OIDCTokenResponse ) OIDCTokenResponseParser .parse (tokenResponseText );
176188
177189 if (!tokenResponse .indicatesSuccess ()) {
178190 TokenErrorResponse errorResponse = tokenResponse .toErrorResponse ();
@@ -181,11 +193,17 @@ public static HttpResponse oidcCallbackResponse(OidcProvider provider, URI reque
181193
182194 OIDCTokenResponse successResponse = tokenResponse .toSuccessResponse ();
183195
184- if (data .isInvalidNonce ((String ) successResponse .getOIDCTokens ().getIDToken ().getJWTClaimsSet ().getClaim ("nonce" ))) {
185- return HttpResponse .ofCode (400 ).withHtml (
186- "Your oidc provider sent an invalid nonce. Try again or contact your oidc admin"
187- );
188- }
196+ JWT idToken = JWTParser .parse (successResponse .getOIDCTokens ().getIDTokenString ());
197+
198+ try {
199+ provider .validator .validate (idToken , data .nonce );
200+ } catch (BadJOSEException e ) {
201+ System .out .println ("Invalid token received: " + e .toString ());
202+ return HttpResponse .ofCode (400 ).withHtml ("Received a bad token. Please try again" );
203+ } catch (JOSEException e ) {
204+ System .out .println ("Token processing error" + e .toString ());
205+ return HttpResponse .ofCode (500 ).withHtml ("Internal processing error. Please try again" );
206+ }
189207
190208 UserInfoRequest ur = new UserInfoRequest (provider .userinfoUri , successResponse .getOIDCTokens ().getBearerAccessToken ());
191209 UserInfoResponse userInfoResponse = UserInfoResponse .parse (ur .toHTTPRequest ().send ());
@@ -200,38 +218,87 @@ public static HttpResponse oidcCallbackResponse(OidcProvider provider, URI reque
200218
201219 UserInfo userInfo = userInfoResponse .toSuccessResponse ().getUserInfo ();
202220
203-
204- String uid = userInfo .getSubject ().toString ();
221+ String sub = userInfo .getSubject ().toString ();
205222 String sessionId ;
206223 try (Session s = DatabaseSessionFactory .createSession ()) {
207- // TODO: Add oidc provider to database
208- String dbName = provider + "-" + uid ;
209224 CriteriaBuilder cb = s .getCriteriaBuilder ();
210- CriteriaQuery <User > cr = cb .createQuery (User .class );
211- Root <User > root = cr .from (User .class );
212- cr .select (root ).where (root .get ("username" ).in (
213- dbName
214- ));
225+ CriteriaQuery <OidcUserData > cr = cb .createQuery (OidcUserData .class );
226+ Root <OidcUserData > root = cr .from (OidcUserData .class );
215227
216- User dbuser = s . createQuery ( cr ). uniqueResult ( );
228+ cr . select ( root ). where ( root . get ( "sub" ). in ( sub ) );
217229
218- if (dbuser == null ) {
219- User newuser = new User (dbName , "" , Set .of ());
230+ OidcUserData dbuser = s .createQuery (cr ).uniqueResult ();
231+
232+ if (dbuser != null ) {
233+ sessionId = dbuser .getUser ().getSessionId ();
234+ } else {
235+ String username = userInfo .getPreferredUsername ();
236+ OidcUserData newUser = new OidcUserData (sub , username , provider .name );
220237
221238 var tr = s .beginTransaction ();
222- s .persist (newuser );
239+ s .persist (newUser );
223240 tr .commit ();
224241
225-
226- sessionId = newuser .getSessionId ();
227- } else sessionId = dbuser .getSessionId ();
242+ sessionId = newUser .getUser ().getSessionId ();
243+ }
228244 }
229245 return HttpResponse .redirect302 (data .data + "?session=" + sessionId );
230246
231247 }
232248
233- public static HttpResponse oidcDeleteResponse (OidcProvider provider , URI requestUri ) throws Exception {
234- ClientAuthentication clientAuth = new ClientSecretBasic (provider .clientID , provider .clientSecret );
249+ public static HttpResponse oidcDeleteRequest (String session ) throws Exception {
250+
251+ if (StringUtils .isBlank (session )) {
252+ return HttpResponse .ofCode (400 ).withHtml ("session is a required parameter" );
253+ }
254+
255+ OidcProvider provider = null ;
256+ try (Session s = DatabaseSessionFactory .createSession ()) {
257+
258+ User user = DatabaseHelper .getUserFromSession (session );
259+
260+ if (user == null ) {
261+ return HttpResponse .ofCode (400 ).withHtml ("User not found" );
262+ }
263+
264+ CriteriaBuilder cb = s .getCriteriaBuilder ();
265+ CriteriaQuery <OidcUserData > cr = cb .createQuery (OidcUserData .class );
266+ Root <OidcUserData > root = cr .from (OidcUserData .class );
267+ cr .select (root ).where (cb .equal (root .get ("user" ), user ));
268+
269+ OidcUserData oidcUserData = s .createQuery (cr ).uniqueResult ();
270+
271+ for (OidcProvider test : Constants .OIDC_PROVIDERS ) {
272+ if (test .name .equals (oidcUserData .getProvider ())) {
273+ provider = test ;
274+ }
275+ }
276+ }
277+
278+ if (provider == null ) {
279+ return HttpResponse .ofCode (400 ).withHtml ("Invalid user" );
280+ }
281+ CodeVerifier pkceVerifier = new CodeVerifier ();
282+
283+ URI callback = URI .create (String .format ("%s/oidc/%s/delete" , Constants .PUBLIC_URL , provider .name ));
284+ OidcData data = new OidcData (session + "|" + Instant .now ().getEpochSecond (), pkceVerifier );
285+ String state = data .getState ();
286+ PENDING_OIDC .put (state , data );
287+
288+ AuthenticationRequest oidcRequest = new AuthenticationRequest .Builder (
289+ new ResponseType ("code" ),
290+ new Scope ("openid" ), provider .clientID , callback )
291+ .endpointURI (provider .authUri )
292+ .codeChallenge (pkceVerifier , CodeChallengeMethod .S256 )
293+ .state (new State (state ))
294+ .nonce (data .nonce )
295+ // This parameter is optional and the idp does't have to honor it.
296+ .maxAge (0 )
297+ .build ();
298+
299+ return HttpResponse .redirect302 (oidcRequest .toURI ().toString ());
300+ }
301+ public static HttpResponse oidcDeleteCallback (OidcProvider provider , URI requestUri ) throws Exception {
235302
236303 AuthenticationSuccessResponse sr = parseOidcUri (requestUri );
237304
@@ -247,8 +314,10 @@ public static HttpResponse oidcDeleteResponse(OidcProvider provider, URI request
247314
248315 URI callback = new URI (Constants .PUBLIC_URL + "/oidc/" + provider .name + "/delete" );
249316 AuthorizationCode code = sr .getAuthorizationCode ();
250- AuthorizationGrant codeGrant = new AuthorizationCodeGrant (code , callback );
317+ AuthorizationGrant codeGrant = new AuthorizationCodeGrant (code , callback , data .pkceVerifier );
318+
251319
320+ ClientAuthentication clientAuth = new ClientSecretBasic (provider .clientID , provider .clientSecret );
252321
253322 TokenRequest tokenRequest = new TokenRequest (provider .tokenUri , clientAuth , codeGrant );
254323 TokenResponse tokenResponse = OIDCTokenResponseParser .parse (tokenRequest .toHTTPRequest ().send ());
@@ -260,15 +329,26 @@ public static HttpResponse oidcDeleteResponse(OidcProvider provider, URI request
260329
261330 OIDCTokenResponse successResponse = (OIDCTokenResponse ) tokenResponse .toSuccessResponse ();
262331
263- JWTClaimsSet claims = successResponse .getOIDCTokens ().getIDToken ().getJWTClaimsSet ();
332+ JWT idToken = JWTParser .parse (successResponse .getOIDCTokens ().getIDTokenString ());
333+
334+ IDTokenClaimsSet claims ;
335+ try {
336+ claims = provider .validator .validate (idToken , data .nonce );
337+ } catch (BadJOSEException e ) {
338+ System .out .println ("Invalid token received: " + e .toString ());
339+ return HttpResponse .ofCode (400 ).withHtml ("Received a bad token. Please try again" );
340+ } catch (JOSEException e ) {
341+ System .out .println ("Token processing error" + e .toString ());
342+ return HttpResponse .ofCode (500 ).withHtml ("Internal processing error. Please try again" );
343+ }
264344
265- if (data .isInvalidNonce ((String ) claims .getClaim ("nonce" ))) {
266- return HttpResponse .ofCode (400 ).withHtml (
267- "Your oidc provider sent an invalid nonce. Please try again or contact your oidc admin."
268- );
269- }
270345
271- long authTime = (long ) claims .getClaim ("auth_time" );
346+
347+ Long authTime = (Long ) claims .getNumberClaim ("auth_time" );
348+
349+ if (authTime == null ) {
350+ return HttpResponse .ofCode (400 ).withHtml ("Couldn't get the `auth_time` claim from the provided id token" );
351+ }
272352
273353 if (authTime < start ) {
274354 return HttpResponse .ofCode (500 ).withHtml (
@@ -277,7 +357,6 @@ public static HttpResponse oidcDeleteResponse(OidcProvider provider, URI request
277357 }
278358
279359 try (Session s = DatabaseSessionFactory .createSession ()) {
280-
281360 var tr = s .beginTransaction ();
282361 s .remove (DatabaseHelper .getUserFromSession (session ));
283362 tr .commit ();
@@ -297,31 +376,6 @@ public static byte[] deleteUserResponse(String session, String pass) throws IOEx
297376
298377 String hash = user .getPassword ();
299378
300- if (hash .isEmpty ()) {
301-
302- CriteriaBuilder cb = s .getCriteriaBuilder ();
303- CriteriaQuery <OidcUserData > cr = cb .createQuery (OidcUserData .class );
304- Root <OidcUserData > root = cr .from (OidcUserData .class );
305- cr .select (root ).where (cb .equal (root .get ("user" ), user .getId ()));
306-
307- OidcUserData oidcUserData = s .createQuery (cr ).uniqueResult ();
308-
309- //TODO: Get user from oidc table and lookup provider
310- OidcProvider provider = Constants .OIDC_PROVIDERS .get (0 );
311- URI callback = URI .create (String .format ("%s/oidc/%s/delete" , Constants .PUBLIC_URL , provider .name ));
312- OidcData data = new OidcData (session + "|" + Instant .now ().getEpochSecond ());
313- String state = data .getState ();
314- PENDING_OIDC .put (state , data );
315-
316- AuthenticationRequest oidcRequest = new AuthenticationRequest .Builder (
317- new ResponseType ("code" ),
318- new Scope ("openid" ), provider .clientID , callback ).endpointURI (provider .authUri )
319- .state (new State (state )).nonce (data .nonce ).maxAge (0 ).build ();
320-
321-
322- return mapper .writeValueAsBytes (mapper .createObjectNode ()
323- .put ("redirect" , oidcRequest .toURI ().toString ()));
324- }
325379 if (!hashMatch (hash , pass ))
326380 ExceptionHandler .throwErrorResponse (new IncorrectCredentialsResponse ());
327381
0 commit comments