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