1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package fr.paris.lutece.plugins.oauth2.web;
35
36 import java.io.IOException;
37 import java.io.Serializable;
38 import java.io.UnsupportedEncodingException;
39 import java.math.BigInteger;
40 import java.net.URLEncoder;
41 import java.security.NoSuchAlgorithmException;
42 import java.security.SecureRandom;
43 import javax.servlet.http.HttpServletRequest;
44 import javax.servlet.http.HttpServletResponse;
45 import javax.servlet.http.HttpSession;
46
47 import org.apache.commons.lang3.StringUtils;
48 import org.apache.log4j.Logger;
49
50 import fr.paris.lutece.plugins.oauth2.business.AuthClientConf;
51 import fr.paris.lutece.plugins.oauth2.business.AuthServerConf;
52 import fr.paris.lutece.plugins.oauth2.business.Token;
53 import fr.paris.lutece.plugins.oauth2.dataclient.DataClient;
54 import fr.paris.lutece.plugins.oauth2.jwt.JWTParser;
55 import fr.paris.lutece.plugins.oauth2.jwt.TokenValidationException;
56 import fr.paris.lutece.plugins.oauth2.service.DataClientService;
57 import fr.paris.lutece.plugins.oauth2.service.PkceUtil;
58 import fr.paris.lutece.plugins.oauth2.service.TokenService;
59 import fr.paris.lutece.portal.service.util.AppLogService;
60 import fr.paris.lutece.portal.service.util.AppPathService;
61 import fr.paris.lutece.portal.service.util.AppPropertiesService;
62 import fr.paris.lutece.util.http.SecurityUtil;
63 import fr.paris.lutece.util.httpaccess.HttpAccessException;
64 import fr.paris.lutece.util.url.UrlItem;
65
66
67
68
69 public class CallbackHandler implements Serializable
70 {
71 private static final String PROPERTY_ERROR_PAGE = "oauth2.error.page";
72 private static final long serialVersionUID = 1L;
73 private static Logger _logger = Logger.getLogger( Constants.LOGGER_OAUTH2 );
74 private String _handlerName;
75 private AuthServerConf _authServerConf;
76 private AuthClientConf _authClientConf;
77 private JWTParser _jWTParser;
78 private boolean _bDefault;
79
80
81
82
83 public AuthServerConf getAuthServerConf( )
84 {
85 return _authServerConf;
86 }
87
88
89
90
91
92 public void setAuthServerConf( AuthServerConf authServerConf )
93 {
94 _authServerConf = authServerConf;
95 }
96
97
98
99
100 public AuthClientConf getAuthClientConf( )
101 {
102 return _authClientConf;
103 }
104
105
106
107
108
109 public void setAuthClientConf( AuthClientConf authClientConf )
110 {
111 _authClientConf = authClientConf;
112 }
113
114
115
116
117
118
119
120
121
122 void handle( HttpServletRequest request, HttpServletResponse response )
123 {
124 String strError = request.getParameter( Constants.PARAMETER_ERROR );
125 String strCode = request.getParameter( Constants.PARAMETER_CODE );
126
127 if ( strError != null )
128 {
129 handleError( request, response, strError );
130 }
131 else
132 if ( strCode != null )
133 {
134 handleAuthorizationCodeResponse( request, response );
135 }
136 else
137 {
138 handleAuthorizationRequest( request, response );
139 }
140 }
141
142
143 void logout( HttpServletRequest request, HttpServletResponse response )
144 {
145 DataClient dataClient = DataClientService.instance( ).getClient( request );
146 UrlItem url = new UrlItem( _authServerConf.getLogoutEndpointUri());
147 url.addParameter( Constants.PARAMETER_CLIENT_ID, _authClientConf.getClientId( ) );
148 try {
149 url.addParameter( Constants.PARAMETER_POST_LOGOUT_REDIRECT_URI, URLEncoder.encode( generatePostLogoutRedirectUri(request, dataClient), "UTF-8" ));
150 } catch (UnsupportedEncodingException e) {
151 _logger.error( "error during urlEncode of param" + Constants.PARAMETER_POST_LOGOUT_REDIRECT_URI, e );
152 }
153 String strIdTokenInt = request.getParameter( Constants.PARAMETER_ID_TOKEN_HINT );
154 if(strIdTokenInt!=null)
155 {
156 url.addParameter( Constants.PARAMETER_ID_TOKEN_HINT, strIdTokenInt );
157 }
158 try
159 {
160 response.sendRedirect( url.getUrl( ) );
161 }
162 catch( IOException ex )
163 {
164 _logger.error( "Error redirecting to the logout page : " + ex.getMessage( ), ex );
165 }
166 }
167
168
169
170
171
172
173
174
175
176
177
178 private void handleError( HttpServletRequest request, HttpServletResponse response, String strError )
179 {
180 DataClient dataClient = DataClientService.instance( ).getClient( request );
181
182 if ( dataClient != null )
183 {
184
185 dataClient.handleError( request, response, strError );
186 }
187 else
188 {
189
190 try
191 {
192 UrlItem url = new UrlItem( AppPathService.getBaseUrl( request ) + AppPropertiesService.getProperty( PROPERTY_ERROR_PAGE ) );
193 url.addParameter( Constants.PARAMETER_ERROR, strError );
194 _logger.info( strError );
195 response.sendRedirect( url.getUrl( ) );
196 }
197 catch( IOException ex )
198 {
199
200 _logger.error( "Error redirecting to the error page : " + ex.getMessage( ), ex );
201 }
202 }
203 }
204
205
206
207
208
209
210
211
212
213 private void handleAuthorizationRequest( HttpServletRequest request, HttpServletResponse response )
214 {
215 try
216 {
217 HttpSession session = request.getSession( true );
218
219 DataClient dataClient = DataClientService.instance( ).getClient( request );
220
221 UrlItem url = new UrlItem( _authServerConf.getAuthorizationEndpointUri( ) );
222 url.addParameter( Constants.PARAMETER_CLIENT_ID, _authClientConf.getClientId( ) );
223 url.addParameter( Constants.PARAMETER_RESPONSE_TYPE, Constants.RESPONSE_TYPE_CODE );
224 url.addParameter( Constants.PARAMETER_REDIRECT_URI, URLEncoder.encode( generateRedirectUrl( request, dataClient ), "UTF-8" ) );
225 url.addParameter( Constants.PARAMETER_SCOPE, dataClient.getScopes( ) );
226 url.addParameter( Constants.PARAMETER_STATE, createState( session ) );
227 url.addParameter( Constants.PARAMETER_NONCE, createNonce( session ) );
228 if(_authClientConf.isPkce())
229 {
230 String strCodeVerifier=createCodeVerifier(session);
231 String strCodeChallenge=createCodeChallenge(strCodeVerifier, session);
232 url.addParameter(Constants.PARAMETER_CODE_CHALLENGE,strCodeChallenge);
233 url.addParameter(Constants.PARAMETER_CODE_CHALLENGE_METHOD,"S256");
234 }
235
236
237
238
239 addComplementaryParameters( url, request );
240 addBackPromptUrl(url, request);
241
242 String strAcrValues = dataClient.getAcrValues( );
243 if ( strAcrValues != null )
244 {
245 url.addParameter( Constants.PARAMETER_ACR_VALUES, strAcrValues );
246 }
247
248 String strUrl = url.getUrl( );
249 _logger.debug( "OAuth request : " + strUrl );
250 response.sendRedirect( strUrl );
251 }
252 catch( IOException ex )
253 {
254 String strError = "Error retrieving an authorization code : " + ex.getMessage( );
255 _logger.error( strError, ex );
256 handleError( request, response, Constants.ERROR_TYPE_RETRIEVING_AUTHORIZATION_CODE );
257 }
258 }
259
260
261
262
263
264
265
266
267
268 private void handleAuthorizationCodeResponse( HttpServletRequest request, HttpServletResponse response )
269 {
270 String strCode = request.getParameter( Constants.PARAMETER_CODE );
271
272 _logger.info( "OAuth Authorization code received : " + SecurityUtil.logForgingProtect( strCode ) );
273
274
275 if ( !checkState( request ) )
276 {
277 handleError( request, response, Constants.ERROR_TYPE_INVALID_STATE );
278
279 return;
280 }
281
282 try
283 {
284 HttpSession session = request.getSession( );
285 DataClient dataClient = DataClientService.instance( ).getClient( request );
286 String strRedirectUri = generateRedirectUrl( request, dataClient );
287 Token token = getToken( strRedirectUri, strCode, session );
288 dataClient.handleToken( token, request, response );
289 }
290 catch( IOException ex )
291 {
292 String strError = "Error retrieving token : " + ex.getMessage( );
293 _logger.error( strError, ex );
294 handleError( request, response, strError );
295 }
296 catch( HttpAccessException ex )
297 {
298 String strError = "Error retrieving token : " + ex.getMessage( );
299 _logger.error( strError, ex );
300 handleError( request, response, strError );
301 }
302 catch( TokenValidationException ex )
303 {
304 String strError = "Error retrieving token : " + ex.getMessage( );
305 _logger.error( strError, ex );
306 handleError( request, response, strError );
307 }
308 }
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327 private Token getToken( String strRedirectUri, String strAuthorizationCode, HttpSession session )
328 throws IOException, HttpAccessException, TokenValidationException
329 {
330
331 return TokenService.getService( ).getToken( strRedirectUri, _authClientConf, _authServerConf, strAuthorizationCode, session, _jWTParser,
332 getStoredNonce( session ),getStoredCodeVerifier(session) );
333
334 }
335
336
337
338
339
340
341
342
343
344
345
346 private String createNonce( HttpSession session )
347 {
348 String nonce = new BigInteger( 128, new SecureRandom( ) ).toString( 16 );
349 session.setAttribute( getNonceAttributeSessionName( ), nonce );
350
351 return nonce;
352 }
353
354
355
356
357
358
359
360
361
362 private String createCodeChallenge(String strCodeVerifier,HttpSession session )
363 {
364
365 String strChallenge=null;
366 try {
367 if(strCodeVerifier!=null)
368 {
369 strChallenge = PkceUtil.generateCodeChallenge(strCodeVerifier);
370 }
371 } catch (UnsupportedEncodingException|NoSuchAlgorithmException e) {
372 AppLogService.error(e);
373 }
374
375 session.setAttribute( getCodeChallengeAttributeSessionName(), strChallenge );
376
377 return strChallenge;
378 }
379
380
381
382
383
384
385
386
387 private String createCodeVerifier( HttpSession session )
388 {
389 String StrCodeVerifier=null;
390 try {
391 StrCodeVerifier = PkceUtil.generateCodeVerifier();
392 } catch (UnsupportedEncodingException e) {
393 AppLogService.error(e);
394 }
395 session.setAttribute( getCodeVerifierAttributeSessionName(), StrCodeVerifier );
396
397 return StrCodeVerifier;
398 }
399
400
401
402
403
404
405
406
407
408
409 private String getStoredCodeVerifier( HttpSession session )
410 {
411 return getStoredSessionString( session, getCodeVerifierAttributeSessionName() );
412 }
413
414
415
416
417
418
419
420
421
422 private String getStoredCodeChallenge( HttpSession session )
423 {
424 return getStoredSessionString( session, getCodeChallengeAttributeSessionName() );
425 }
426
427
428
429
430
431
432
433
434
435
436
437 private String getStoredNonce( HttpSession session )
438 {
439 return getStoredSessionString( session, getNonceAttributeSessionName( ) );
440 }
441
442
443
444
445
446
447
448 private String getNonceAttributeSessionName( )
449 {
450
451 return getHandlerName( ) == null ? Constants.NONCE_SESSION_VARIABLE : getHandlerName( ) + Constants.NONCE_SESSION_VARIABLE;
452 }
453
454
455
456
457
458 private String getCodeVerifierAttributeSessionName( )
459 {
460
461 return getHandlerName( ) == null ? Constants.CODE_VERIFIER_SESSION_VARIABLE : getHandlerName( ) + Constants.CODE_VERIFIER_SESSION_VARIABLE;
462 }
463
464
465
466
467
468
469 private String getCodeChallengeAttributeSessionName( )
470 {
471
472 return getHandlerName( ) == null ? Constants.CODE_CHALLENGE_SESSION_VARIABLE : getHandlerName( ) + Constants.CODE_CHALLENGE_SESSION_VARIABLE;
473 }
474
475
476
477
478
479
480
481
482
483
484
485 private boolean checkState( HttpServletRequest request )
486 {
487 String strState = request.getParameter( Constants.PARAMETER_STATE );
488 HttpSession session = request.getSession( );
489 String strStored = getStoredState( session );
490 boolean bCheck = ( ( strState == null ) || strState.equals( strStored ) );
491
492 if ( !bCheck )
493 {
494 _logger.debug( "Bad state returned by server : " + SecurityUtil.logForgingProtect( strState ) + " while expecting : " + strStored );
495 }
496
497 return bCheck;
498 }
499
500
501
502
503
504
505
506
507 private String createState( HttpSession session )
508 {
509 String strState = new BigInteger( 128, new SecureRandom( ) ).toString( 16 );
510 session.setAttribute( getStateAttributeSessionName( ), strState );
511
512 return strState;
513 }
514
515
516
517
518
519 private String getStateAttributeSessionName( )
520 {
521
522 return getHandlerName( ) == null ? Constants.STATE_SESSION_VARIABLE : getHandlerName( ) + Constants.STATE_SESSION_VARIABLE;
523 }
524
525
526
527
528
529
530
531
532 private String getStoredState( HttpSession session )
533 {
534 return getStoredSessionString( session, getStateAttributeSessionName( ) );
535 }
536
537
538
539
540
541
542
543
544
545
546 private static String getStoredSessionString( HttpSession session, String strKey )
547 {
548 Object object = session.getAttribute( strKey );
549
550 if ( ( object != null ) && object instanceof String )
551 {
552 return object.toString( );
553 }
554 else
555 {
556 return null;
557 }
558 }
559
560
561
562
563
564
565 public String getHandlerName( )
566 {
567 return _handlerName;
568 }
569
570
571
572
573
574
575
576 public void setHandlerName( String _handlerName )
577 {
578 this._handlerName = _handlerName;
579 }
580
581
582
583
584
585 public JWTParser getJWTParser( )
586 {
587 return _jWTParser;
588 }
589
590
591
592
593
594
595 public void setJWTParser( JWTParser jWTParser )
596 {
597 this._jWTParser = jWTParser;
598 }
599
600
601
602
603
604 public boolean isDefault( )
605 {
606 return _bDefault;
607 }
608
609
610
611
612
613
614 public void setDefault( boolean _bDefault )
615 {
616 this._bDefault = _bDefault;
617 }
618
619 private void addComplementaryParameters( UrlItem url, HttpServletRequest request )
620 {
621 String[] strComplementaryParams = request.getParameterValues( Constants.PARAMETER_COMPLEMENTARY_PARAMETER );
622 String strParamValue;
623 String strParamCode;
624
625 if ( strComplementaryParams!=null && strComplementaryParams.length > 0 )
626 {
627 for (int i = 0; i < strComplementaryParams.length; i++) {
628
629 if(strComplementaryParams[i].contains( "=" ))
630 {
631 strParamCode = strComplementaryParams[i].split( "=" ) [0];
632 strParamValue = strComplementaryParams[i].substring( strComplementaryParams[i].indexOf( "=" ) + 1, strComplementaryParams[i].length( ) );
633 try
634 {
635 strParamValue = URLEncoder.encode( strParamValue, "UTF-8" );
636 }
637 catch( UnsupportedEncodingException e )
638 {
639 _logger.error( "error during urlEncode of param" + strParamValue, e );
640 }
641 url.addParameter( strParamCode, strParamValue );
642 }
643
644 }
645
646 }
647
648 }
649
650
651
652 private void addBackPromptUrl( UrlItem url, HttpServletRequest request )
653 {
654 String strBackPromptUrl= request.getParameter( Constants.PARAMETER_BACK_PROMPT_URL);
655
656 if ( !StringUtils.isEmpty( strBackPromptUrl) )
657 {
658 try
659 {
660
661 url.addParameter( Constants.PARAMETER_BACK_PROMPT_URL, URLEncoder.encode( strBackPromptUrl, "UTF-8" ) );
662
663 }
664 catch( UnsupportedEncodingException e )
665 {
666 _logger.error( "error during urlEncode of param " +Constants.PARAMETER_BACK_PROMPT_URL + strBackPromptUrl, e );
667 }
668
669 }
670
671 }
672
673
674
675
676
677
678
679
680
681
682
683
684 private String generateRedirectUrl( HttpServletRequest request, DataClient dataClient )
685 {
686 String stRedirectUrl = _authClientConf.getRedirectUri( );
687 UrlItem url = new UrlItem(stRedirectUrl);
688
689 if ( stRedirectUrl == null )
690 {
691 stRedirectUrl = DataClientService.instance( ).getDataClientUrl( request, dataClient.getName( ), getHandlerName( ) );
692 url=new UrlItem(stRedirectUrl);
693
694 }
695 else
696 {
697
698
699 url.addParameter(Constants.PARAMETER_DATA_CLIENT, dataClient.getName( ));
700 if ( getHandlerName( ) != null )
701 {
702 url.addParameter(Constants.PARAMETER_HANDLER_NAME, getHandlerName( ));
703
704 }
705 }
706
707 String strBackPromptUrl= request.getParameter( Constants.PARAMETER_BACK_PROMPT_URL);
708
709 if ( !StringUtils.isEmpty( strBackPromptUrl) )
710 {
711
712 try {
713
714 url.addParameter(Constants.PARAMETER_BACK_PROMPT_URL,URLEncoder.encode(strBackPromptUrl, "UTF-8"));
715
716 } catch (UnsupportedEncodingException e) {
717 _logger.error(
718 "error during urlEncode of param " + Constants.PARAMETER_BACK_PROMPT_URL + strBackPromptUrl, e);
719 }
720
721 }
722
723 return url.getUrl();
724 }
725
726
727
728
729
730
731
732
733
734
735
736
737 private String generatePostLogoutRedirectUri( HttpServletRequest request, DataClient dataClient )
738 {
739 String strPostLogoutRedirectUri = _authClientConf.getPostLogoutRedirectUri( );
740 UrlItem url = new UrlItem(strPostLogoutRedirectUri!=null && StringUtils.isNotEmpty(strPostLogoutRedirectUri)?strPostLogoutRedirectUri:AppPathService.getBaseUrl( request ) );
741 return url.getUrl();
742 }
743
744 }