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.franceconnect.web;
35
36 import fr.paris.lutece.plugins.franceconnect.oidc.AuthClientConf;
37 import fr.paris.lutece.plugins.franceconnect.oidc.AuthServerConf;
38 import fr.paris.lutece.plugins.franceconnect.oidc.Token;
39 import fr.paris.lutece.plugins.franceconnect.oidc.dataclient.DataClient;
40 import fr.paris.lutece.plugins.franceconnect.oidc.jwt.TokenValidationException;
41 import fr.paris.lutece.plugins.franceconnect.service.DataClientService;
42 import fr.paris.lutece.plugins.franceconnect.service.TokenService;
43 import fr.paris.lutece.portal.service.util.AppPathService;
44 import fr.paris.lutece.portal.service.util.AppPropertiesService;
45 import fr.paris.lutece.util.httpaccess.HttpAccess;
46 import fr.paris.lutece.util.httpaccess.HttpAccessException;
47 import fr.paris.lutece.util.url.UrlItem;
48
49 import org.apache.log4j.Logger;
50
51 import java.io.IOException;
52
53 import java.math.BigInteger;
54
55 import java.net.URLEncoder;
56
57 import java.security.SecureRandom;
58
59 import java.util.Map;
60 import java.util.concurrent.ConcurrentHashMap;
61
62 import javax.servlet.http.HttpServletRequest;
63 import javax.servlet.http.HttpServletResponse;
64 import javax.servlet.http.HttpSession;
65
66
67
68
69
70 public class CallbackHandler
71 {
72 private static final String PROPERTY_ERROR_PAGE = "franceconnect.error.page";
73 private static final long serialVersionUID = 1L;
74 private static Logger _logger = Logger.getLogger( Constants.LOGGER_FRANCECONNECT );
75 private AuthServerConf _authServerConf;
76 private AuthClientConf _authClientConf;
77
78
79
80
81 public AuthServerConf getAuthServerConf( )
82 {
83 return _authServerConf;
84 }
85
86
87
88
89 public void setAuthServerConf( AuthServerConf authServerConf )
90 {
91 _authServerConf = authServerConf;
92 }
93
94
95
96
97 public AuthClientConf getAuthClientConf( )
98 {
99 return _authClientConf;
100 }
101
102
103
104
105 public void setAuthClientConf( AuthClientConf authClientConf )
106 {
107 _authClientConf = authClientConf;
108 }
109
110
111
112
113
114
115 void handle( HttpServletRequest request, HttpServletResponse response )
116 {
117 String strError = request.getParameter( Constants.PARAMETER_ERROR );
118 String strCode = request.getParameter( Constants.PARAMETER_CODE );
119
120 if ( strError != null )
121 {
122 handleError( request, response, strError );
123 }
124 else if ( strCode != null )
125 {
126 handleAuthorizationCodeResponse( request, response );
127 }
128 else
129 {
130 handleAuthorizationRequest( request, response );
131 }
132 }
133
134
135
136
137
138
139
140
141 private void handleError( HttpServletRequest request, HttpServletResponse response, String strError )
142 {
143 try
144 {
145 UrlItem url = new UrlItem( AppPathService.getBaseUrl( request ) +
146 AppPropertiesService.getProperty( PROPERTY_ERROR_PAGE ) );
147 url.addParameter( Constants.PARAMETER_ERROR, strError );
148 _logger.info( strError );
149 response.sendRedirect( url.getUrl( ) );
150 }
151 catch ( IOException ex )
152 {
153 _logger.error( "Error redirecting to the error page : " + ex.getMessage( ), ex );
154 }
155 }
156
157
158
159
160
161
162 private void handleAuthorizationRequest( HttpServletRequest request, HttpServletResponse response )
163 {
164 try
165 {
166 HttpSession session = request.getSession( true );
167
168 String strDataClientName = request.getParameter( Constants.PARAMETER_DATA_CLIENT );
169 DataClient dataClient = DataClientService.instance( ).getClient( strDataClientName );
170 session.setAttribute( Constants.SESSION_ATTRIBUTE_DATACLIENT, dataClient );
171
172 UrlItem url = new UrlItem( _authServerConf.getAuthorizationEndpointUri( ) );
173 url.addParameter( Constants.PARAMETER_CLIENT_ID, _authClientConf.getClientId( ) );
174 url.addParameter( Constants.PARAMETER_RESPONSE_TYPE, Constants.RESPONSE_TYPE_CODE );
175 url.addParameter( Constants.PARAMETER_REDIRECT_URI,
176 URLEncoder.encode( _authClientConf.getRedirectUri( ), "UTF-8" ) );
177 url.addParameter( Constants.PARAMETER_SCOPE, dataClient.getScopes( ) );
178 url.addParameter( Constants.PARAMETER_STATE, createState( session ) );
179 url.addParameter( Constants.PARAMETER_NONCE, createNonce( session ) );
180
181 String strAcrValues = dataClient.getAcrValues();
182 if( strAcrValues != null )
183 {
184 url.addParameter( Constants.PARAMETER_ACR_VALUES, strAcrValues );
185 }
186
187 String strUrl = url.getUrl( );
188 _logger.debug( "OAuth request : " + strUrl );
189 response.sendRedirect( strUrl );
190 }
191 catch ( IOException ex )
192 {
193 String strError = "Error retrieving an authorization code : " + ex.getMessage( );
194 _logger.error( strError, ex );
195 handleError( request, response, strError );
196 }
197 }
198
199
200
201
202
203
204
205 private void handleAuthorizationCodeResponse( HttpServletRequest request, HttpServletResponse response )
206 {
207 String strCode = request.getParameter( Constants.PARAMETER_CODE );
208 _logger.info( "OAuth Authorization code received : " + strCode );
209
210
211 if ( !checkState( request ) )
212 {
213 handleError( request, response, "Invalid state returned by FranceConnect !" );
214
215 return;
216 }
217
218 try
219 {
220 HttpSession session = request.getSession( );
221 Token token = getToken( strCode, session );
222 DataClient dataClient = (DataClient) session.getAttribute( Constants.SESSION_ATTRIBUTE_DATACLIENT );
223 dataClient.handleToken( token , request , response );
224 }
225 catch ( IOException ex )
226 {
227 String strError = "Error retrieving token : " + ex.getMessage( );
228 _logger.error( strError, ex );
229 handleError( request, response, strError );
230 }
231 catch ( HttpAccessException ex )
232 {
233 String strError = "Error retrieving token : " + ex.getMessage( );
234 _logger.error( strError, ex );
235 handleError( request, response, strError );
236 }
237 catch ( TokenValidationException ex )
238 {
239 String strError = "Error retrieving token : " + ex.getMessage( );
240 _logger.error( strError, ex );
241 handleError( request, response, strError );
242 }
243 }
244
245
246
247
248
249
250
251
252
253
254 private Token getToken( String strAuthorizationCode, HttpSession session )
255 throws IOException, HttpAccessException, TokenValidationException
256 {
257 String strRedirectUri = _authClientConf.getRedirectUri( );
258 Map<String, String> mapParameters = new ConcurrentHashMap<String, String>( );
259 mapParameters.put( Constants.PARAMETER_GRANT_TYPE, Constants.GRANT_TYPE_CODE );
260 mapParameters.put( Constants.PARAMETER_CODE, strAuthorizationCode );
261 mapParameters.put( Constants.PARAMETER_CLIENT_ID, _authClientConf.getClientId( ) );
262 mapParameters.put( Constants.PARAMETER_CLIENT_SECRET, _authClientConf.getClientSecret( ) );
263
264 if ( strRedirectUri != null )
265 {
266 mapParameters.put( Constants.PARAMETER_REDIRECT_URI, strRedirectUri );
267 }
268
269 HttpAccess httpAccess = new HttpAccess( );
270 String strUrl = _authServerConf.getTokenEndpointUri( );
271
272 _logger.debug( "Posted URL : " + strUrl + "\nParameters :\n" + traceMap( mapParameters ) );
273
274 String strResponse = httpAccess.doPost( strUrl, mapParameters );
275 _logger.debug( "FranceConnect response : " + strResponse );
276
277 return TokenService.parse( strResponse, _authClientConf, _authServerConf, getStoredNonce( session ) );
278 }
279
280
281
282
283
284
285
286
287
288
289 private static String createNonce( HttpSession session )
290 {
291 String nonce = new BigInteger( 50, new SecureRandom( ) ).toString( 16 );
292 session.setAttribute( Constants.NONCE_SESSION_VARIABLE, nonce );
293
294 return nonce;
295 }
296
297
298
299
300
301
302
303 private static String getStoredNonce( HttpSession session )
304 {
305 return getStoredSessionString( session, Constants.NONCE_SESSION_VARIABLE );
306 }
307
308
309
310
311
312
313 private boolean checkState( HttpServletRequest request )
314 {
315 String strState = request.getParameter( Constants.PARAMETER_STATE );
316 HttpSession session = request.getSession( );
317 String strStored = getStoredState( session );
318 boolean bCheck = ( ( strState == null ) || strState.equals( strStored ) );
319
320 if ( !bCheck )
321 {
322 _logger.debug( "Bad state returned by server : " + strState + " while expecting : " + strStored );
323 }
324
325 return bCheck;
326 }
327
328
329
330
331
332
333
334 private static String createState( HttpSession session )
335 {
336 String strState = new BigInteger( 50, new SecureRandom( ) ).toString( 16 );
337 session.setAttribute( Constants.STATE_SESSION_VARIABLE, strState );
338
339 return strState;
340 }
341
342
343
344
345
346
347
348 private static String getStoredState( HttpSession session )
349 {
350 return getStoredSessionString( session, Constants.STATE_SESSION_VARIABLE );
351 }
352
353
354
355
356
357
358
359
360
361 private static String getStoredSessionString( HttpSession session, String strKey )
362 {
363 Object object = session.getAttribute( strKey );
364
365 if ( ( object != null ) && object instanceof String )
366 {
367 return object.toString( );
368 }
369 else
370 {
371 return null;
372 }
373 }
374
375
376
377
378
379
380 private String traceMap( Map<String, String> map )
381 {
382 StringBuilder sbTrace = new StringBuilder( );
383
384 for ( Map.Entry entry : map.entrySet( ) )
385 {
386 sbTrace.append( entry.getKey( ) ).append( ":[" ).append( entry.getValue( ) ).append( "]\n" );
387 }
388
389 return sbTrace.toString( );
390 }
391 }