View Javadoc
1   /*
2    * Copyright (c) 2002-2025, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.oauth2.business;
35  
36  import java.net.URI;
37  import java.net.URISyntaxException;
38  import java.util.Arrays;
39  import java.util.Objects;
40  
41  import com.fasterxml.jackson.annotation.JsonProperty;
42  
43  import fr.paris.lutece.portal.service.util.AppException;
44  
45  /**
46   * OpenID Configuration
47   * 
48   * @see https://openid.net/specs/openid-connect-discovery-1_0.html
49   * @see https://openid.net/specs/openid-connect-rpinitiated-1_0.html
50   * @since 2.0.0
51   */
52  public class OpenIDConfiguration
53  {
54      private String _strIssuer;
55      private String _strAuthorizationEndpoint;
56      private String _strTokenEndpoint;
57      private String _strUserinfoEndpoint;
58      private String _strJwksURI;
59      private String _strRegistrationEndpoint;
60      private String [ ] _scopesSupported;
61      private String [ ] _responseTypesSupported;
62      private String [ ] _responseModesSupported;
63      private String [ ] _grantTypesSupported;
64      private String [ ] _acrValuesSupported;
65      private String [ ] _subjectTypesSupported;
66      private String [ ] _idTokenSigningAlgValuesSupported;
67      private String [ ] _idTokenEncryptionAlgValuesSupported;
68      private String [ ] _idTokenEncryptionEncValuesSupported;
69      private String [ ] _userinfoSigningAlgValuesSupported;
70      private String [ ] _userinfoEncryptionAlgValuesSupported;
71      private String [ ] _userinfoEncryptionEncValuesSupported;
72      private String [ ] _requestObjectSigningAlgValuesSupported;
73      private String [ ] _requestObjectEncryptionAlgValuesSupported;
74      private String [ ] _requestObjectEncryptionEncValuesSupported;
75      private String [ ] _tokenEndpointAuthMethodsSupported;
76      private String [ ] _tokenEndpointAuthSigningAlgValuesSupported;
77      private String [ ] _displayValuesSupported;
78      private String [ ] _claimTypesSupported;
79      private String [ ] _claimsSupported;
80      private String _serviceDocumentation;
81      private String [ ] _claimsLocalesSupported;
82      private String [ ] _uiLocalesSupported;
83      private boolean _claimsParameterSupported;
84      private boolean _requestParameterSupported;
85      private boolean _requestUIRParameterSupported = true;
86      private boolean _requireRequestUIRRegistration;
87      private String _opPolicyURI;
88      private String _opTOSURI;
89      private String _strEndSessionEndpoint;
90  
91      public String getIssuer( )
92      {
93          return _strIssuer;
94      }
95  
96      public void setIssuer( String strIssuer )
97      {
98          _strIssuer = strIssuer;
99      }
100 
101     @JsonProperty( "authorization_endpoint" )
102     public String getAuthorizationEndpoint( )
103     {
104         return _strAuthorizationEndpoint;
105     }
106 
107     @JsonProperty( "authorization_endpoint" )
108     public void getAuthorizationEndpoint( String strAuthorizationEndpoint )
109     {
110         _strAuthorizationEndpoint = strAuthorizationEndpoint;
111     }
112 
113     public String getTokenEndpoint( )
114     {
115         return _strTokenEndpoint;
116     }
117 
118     @JsonProperty( "token_endpoint" )
119     public void setTokenEndpoint( String strTokenEndpoint )
120     {
121         _strTokenEndpoint = strTokenEndpoint;
122     }
123 
124     public String getUserinfoEndpoint( )
125     {
126         return _strUserinfoEndpoint;
127     }
128 
129     @JsonProperty( "userinfo_endpoint" )
130     public void setUserinfoEndpoint( String strUserinfoEndpoint )
131     {
132         _strUserinfoEndpoint = strUserinfoEndpoint;
133     }
134 
135     public String getJwksURI( )
136     {
137         return _strJwksURI;
138     }
139 
140     @JsonProperty( "jwks_uri" )
141     public void setJwksUri( String strJwksURI )
142     {
143         _strJwksURI = strJwksURI;
144     }
145 
146     public String getRegistrationEndpoint( )
147     {
148         return _strRegistrationEndpoint;
149     }
150 
151     @JsonProperty( "registration_endpoint" )
152     public void setRegistrationEndpoint( String strRegistrationEndpoint )
153     {
154         _strRegistrationEndpoint = strRegistrationEndpoint;
155     }
156 
157     public String [ ] getScopesSupported( )
158     {
159         return _scopesSupported;
160     }
161 
162     @JsonProperty( "scopes_supported" )
163     public void setScopesSupported( String [ ] scopesSupported )
164     {
165         this._scopesSupported = scopesSupported;
166     }
167 
168     public String [ ] getResponseTypesSupported( )
169     {
170         return _responseTypesSupported;
171     }
172 
173     @JsonProperty( "response_types_supported" )
174     public void setResponseTypesSupported( String [ ] responseTypesSupported )
175     {
176         this._responseTypesSupported = responseTypesSupported;
177     }
178 
179     public String [ ] getResponseModesSupported( )
180     {
181         return _responseModesSupported;
182     }
183 
184     @JsonProperty( "response_modes_supported" )
185     public void setResponseModesSupported( String [ ] responseModesSupported )
186     {
187         this._responseModesSupported = responseModesSupported;
188     }
189 
190     public String [ ] getGrantTypesSupported( )
191     {
192         return _grantTypesSupported;
193     }
194 
195     @JsonProperty( "grant_types_supported" )
196     public void setGrantTypesSupported( String [ ] grantTypesSupported )
197     {
198         this._grantTypesSupported = grantTypesSupported;
199     }
200 
201     public String [ ] getAcrValuesSupported( )
202     {
203         return _acrValuesSupported;
204     }
205 
206     @JsonProperty( "acr_values_supported" )
207     public void setAcrValuesSupported( String [ ] acrValuesSupported )
208     {
209         this._acrValuesSupported = acrValuesSupported;
210     }
211 
212     public String [ ] getSubjectTypesSupported( )
213     {
214         return _subjectTypesSupported;
215     }
216 
217     @JsonProperty( "subject_types_supported" )
218     public void setSubjectTypesSupported( String [ ] subjectTypesSupported )
219     {
220         this._subjectTypesSupported = subjectTypesSupported;
221     }
222 
223     public String [ ] getIDTokenSigningAlgValuesSupported( )
224     {
225         return _idTokenSigningAlgValuesSupported;
226     }
227 
228     @JsonProperty( "id_token_signing_alg_values_supported" )
229     public void setIDTokenSigningAlgValuesSupported( String [ ] idTokenSigningAlgValuesSupported )
230     {
231         this._idTokenSigningAlgValuesSupported = idTokenSigningAlgValuesSupported;
232     }
233 
234     public String [ ] getIDTokenEncryptionAlgValuesSupported( )
235     {
236         return _idTokenEncryptionAlgValuesSupported;
237     }
238 
239     @JsonProperty( "id_token_encryption_alg_values_supported" )
240     public void setIDTokenEncryptionAlgValuesSupported( String [ ] idTokenEncryptionAlgValuesSupported )
241     {
242         this._idTokenEncryptionAlgValuesSupported = idTokenEncryptionAlgValuesSupported;
243     }
244 
245     public String [ ] getIDTokenEncryptionEncValuesSupported( )
246     {
247         return _idTokenEncryptionEncValuesSupported;
248     }
249 
250     @JsonProperty( "id_token_encryption_enc_values_supported" )
251     public void setIDTokenEncryptionEncValuesSupported( String [ ] _idTokenEncryptionEncValuesSupported )
252     {
253         this._idTokenEncryptionEncValuesSupported = _idTokenEncryptionEncValuesSupported;
254     }
255 
256     public String [ ] getUserinfoSigningAlgValuesSupported( )
257     {
258         return _userinfoSigningAlgValuesSupported;
259     }
260 
261     @JsonProperty( "userinfo_signing_alg_values_supported" )
262     public void setUserinfoSigningAlgValuesSupported( String [ ] userinfoSigningAlgValuesSupported )
263     {
264         this._userinfoSigningAlgValuesSupported = userinfoSigningAlgValuesSupported;
265     }
266 
267     public String [ ] getUserinfoEncryptionAlgValuesSupported( )
268     {
269         return _userinfoEncryptionAlgValuesSupported;
270     }
271 
272     @JsonProperty( "userinfo_encryption_alg_values_supported" )
273     public void setUserinfoEncryptionAlgValuesSupported( String [ ] userinfoEncryptionAlgValuesSupported )
274     {
275         this._userinfoEncryptionAlgValuesSupported = userinfoEncryptionAlgValuesSupported;
276     }
277 
278     public String [ ] getUserinfoEncryptionEncValuesSupported( )
279     {
280         return _userinfoEncryptionEncValuesSupported;
281     }
282 
283     @JsonProperty( "userinfo_encryption_enc_values_supported" )
284     public void setUserinfoEncryptionEncValuesSupported( String [ ] userinfoEncryptionEncValuesSupported )
285     {
286         this._userinfoEncryptionEncValuesSupported = userinfoEncryptionEncValuesSupported;
287     }
288 
289     public String [ ] getRequestObjectSigningAlgValuesSupported( )
290     {
291         return _requestObjectSigningAlgValuesSupported;
292     }
293 
294     @JsonProperty( "request_object_signing_alg_values_supported" )
295     public void setRequestObjectSigningAlgValuesSupported( String [ ] requestObjectSigningAlgValuesSupported )
296     {
297         this._requestObjectSigningAlgValuesSupported = requestObjectSigningAlgValuesSupported;
298     }
299 
300     public String [ ] getRequestObjectEncryptionAlgValuesSupported( )
301     {
302         return _requestObjectEncryptionAlgValuesSupported;
303     }
304 
305     @JsonProperty( "request_object_encryption_alg_values_supported" )
306     public void setRequestObjectEncryptionAlgValuesSupported( String [ ] requestObjectEncryptionAlgValuesSupported )
307     {
308         this._requestObjectEncryptionAlgValuesSupported = requestObjectEncryptionAlgValuesSupported;
309     }
310 
311     public String [ ] getRequestObjectEncryptionEncValuesSupported( )
312     {
313         return _requestObjectEncryptionEncValuesSupported;
314     }
315 
316     @JsonProperty( "request_object_encryption_enc_values_supported" )
317     public void setRequestObjectEncryptionEncValuesSupported( String [ ] requestObjectEncryptionEncValuesSupported )
318     {
319         this._requestObjectEncryptionEncValuesSupported = requestObjectEncryptionEncValuesSupported;
320     }
321 
322     public String [ ] getTokenEndpointAuthMethodsSupported( )
323     {
324         return _tokenEndpointAuthMethodsSupported;
325     }
326 
327     @JsonProperty( "token_endpoint_auth_methods_supported" )
328     public void setTokenEndpointAuthMethodsSupported( String [ ] tokenEndpointAuthMethodsSupported )
329     {
330         this._tokenEndpointAuthMethodsSupported = tokenEndpointAuthMethodsSupported;
331     }
332 
333     public String [ ] getTokenEndpointAuthSigningAlgValuesSupported( )
334     {
335         return _tokenEndpointAuthSigningAlgValuesSupported;
336     }
337 
338     @JsonProperty( "token_endpoint_auth_signing_alg_values_supported" )
339     public void setTokenEndpointAuthSigningAlgValuesSupported( String [ ] tokenEndpointAuthSigningAlgValuesSupported )
340     {
341         this._tokenEndpointAuthSigningAlgValuesSupported = tokenEndpointAuthSigningAlgValuesSupported;
342     }
343 
344     public String [ ] getDisplayValuesSupported( )
345     {
346         return _displayValuesSupported;
347     }
348 
349     @JsonProperty( "display_values_supported" )
350     public void setDisplayValuesSupported( String [ ] displayValuesSupported )
351     {
352         this._displayValuesSupported = displayValuesSupported;
353     }
354 
355     public String [ ] getClaimTypesSupported( )
356     {
357         return _claimTypesSupported;
358     }
359 
360     @JsonProperty( "claim_types_supported" )
361     public void setClaimTypesSupported( String [ ] claimTypesSupported )
362     {
363         this._claimTypesSupported = claimTypesSupported;
364     }
365 
366     public String [ ] getClaimsSupported( )
367     {
368         return _claimsSupported;
369     }
370 
371     @JsonProperty( "claims_supported" )
372     public void setClaimsSupported( String [ ] claimsSupported )
373     {
374         this._claimsSupported = claimsSupported;
375     }
376 
377     public String getServiceDocumentation( )
378     {
379         return _serviceDocumentation;
380     }
381 
382     @JsonProperty( "service_documentation" )
383     public void setServiceDocumentation( String serviceDocumentation )
384     {
385         this._serviceDocumentation = serviceDocumentation;
386     }
387 
388     public String [ ] getClaimsLocalesSupported( )
389     {
390         return _claimsLocalesSupported;
391     }
392 
393     @JsonProperty( "claims_locales_supported" )
394     public void setClaimsLocalesSupported( String [ ] claimsLocalesSupported )
395     {
396         this._claimsLocalesSupported = claimsLocalesSupported;
397     }
398 
399     public String [ ] getUILocalesSupported( )
400     {
401         return _uiLocalesSupported;
402     }
403 
404     @JsonProperty( "ui_locales_supported" )
405     public void setUILocalesSupported( String [ ] uiLocalesSupported )
406     {
407         this._uiLocalesSupported = uiLocalesSupported;
408     }
409 
410     public boolean isClaimsParameterSupported( )
411     {
412         return _claimsParameterSupported;
413     }
414 
415     @JsonProperty( "claims_parameter_supported" )
416     public void setClaimsParameterSupported( boolean claimsParameterSupported )
417     {
418         this._claimsParameterSupported = claimsParameterSupported;
419     }
420 
421     public boolean isRequestParameterSupported( )
422     {
423         return _requestParameterSupported;
424     }
425 
426     @JsonProperty( "request_parameter_supported" )
427     public void setRequestParameterSupported( boolean requestParameterSupported )
428     {
429         this._requestParameterSupported = requestParameterSupported;
430     }
431 
432     public boolean isRequestUIRParameterSupported( )
433     {
434         return _requestUIRParameterSupported;
435     }
436 
437     @JsonProperty( "request_uri_parameter_supported" )
438     public void setRequestUIRParameterSupported( boolean requestUIRParameterSupported )
439     {
440         this._requestUIRParameterSupported = requestUIRParameterSupported;
441     }
442 
443     public boolean isRequireRequestUIRRegistration( )
444     {
445         return _requireRequestUIRRegistration;
446     }
447 
448     @JsonProperty( "require_request_uri_registration" )
449     public void setRequireRequestUIRRegistration( boolean requireRequestUIRRegistration )
450     {
451         this._requireRequestUIRRegistration = requireRequestUIRRegistration;
452     }
453 
454     public String getOpPolicyURI( )
455     {
456         return _opPolicyURI;
457     }
458 
459     @JsonProperty( "op_policy_uri" )
460     public void setOpPolicyURI( String opPolicyURI )
461     {
462         this._opPolicyURI = opPolicyURI;
463     }
464 
465     public String getOpTOSURI( )
466     {
467         return _opTOSURI;
468     }
469 
470     @JsonProperty( "op_tos_uri" )
471     public void setOpTOSURI( String opTOSURI )
472     {
473         this._opTOSURI = opTOSURI;
474     }
475 
476     public String getEndSessionEndpoint( )
477     {
478         return _strEndSessionEndpoint;
479     }
480 
481     @JsonProperty( "end_session_endpoint" )
482     public void setEndSessionEndpoint( String strEndSessionEndpoint )
483     {
484         this._strEndSessionEndpoint = strEndSessionEndpoint;
485     }
486 
487     /**
488      * Validate the configuration.
489      * 
490      * @param strExpectedIssuer
491      *            the expected issuer
492      * @throws NullPointerException
493      *             if a required parameter is absent
494      * @throws AppException
495      *             if another error is present
496      */
497     public void validate( String strExpectedIssuer )
498     {
499         validateIssuer( strExpectedIssuer );
500         validateAuthorizationEndpoint( );
501         validateTokenEndpoint( );
502         validateUserinfoEndpoint( );
503         validateJwksURI( );
504         validateRegistrationEndpoint( );
505         validateScopesSupported( );
506         validateResponseTypeSupported( );
507         validateSubjectTypesSupported( );
508         validateIDTokenSigningAlgValuesSupported( );
509         validateTokenEndpointAuthSigningAlgValuesSupported( );
510         validateEndSessionEndpoint( );
511     }
512 
513     private void validateIssuer( String strExpectedIssuer )
514     {
515         Objects.requireNonNull( _strIssuer, "issuer is required" );
516         validateURI( _strIssuer, "issuer", false, false );
517         if ( !_strIssuer.equals( strExpectedIssuer ) )
518         {
519             throw new AppException( "Expected issuer " + strExpectedIssuer + ", but got " + _strIssuer );
520         }
521     }
522 
523     private void validateAuthorizationEndpoint( )
524     {
525         Objects.requireNonNull( _strAuthorizationEndpoint, "Authorization endpoint is required" );
526         validateURI( _strAuthorizationEndpoint, "Authorization endpoint" );
527     }
528 
529     private void validateTokenEndpoint( )
530     {
531         if ( _strTokenEndpoint == null )
532         {
533             // FIXME This is REQUIRED unless only the Implicit Flow is used
534             return;
535         }
536         validateURI( _strTokenEndpoint, "Token endpoint" );
537     }
538 
539     private void validateUserinfoEndpoint( )
540     {
541         if ( _strUserinfoEndpoint == null )
542         {
543             return;
544         }
545         validateURI( _strUserinfoEndpoint, "Userinfo endpoint" );
546     }
547 
548     private void validateJwksURI( )
549     {
550         Objects.requireNonNull( _strJwksURI, "JWKS URI is required" );
551         validateURI( _strJwksURI, "JWKS URI" );
552     }
553 
554     private void validateRegistrationEndpoint( )
555     {
556         if ( _strRegistrationEndpoint == null )
557         {
558             return;
559         }
560         validateURI( _strRegistrationEndpoint, "Registration endpoint" );
561     }
562 
563     private void validateScopesSupported( )
564     {
565         // The server MUST support the openid scope value. Servers MAY choose not to advertise some supported scope values even when this parameter is used,
566         // although those defined in [OpenID.Core] SHOULD be listed, if supported.
567     }
568 
569     private void validateResponseTypeSupported( )
570     {
571         Objects.requireNonNull( _responseTypesSupported, "Response types supported is required" );
572         for ( String type : _responseTypesSupported )
573         {
574             Objects.requireNonNull( type, "response type must not be null" );
575         }
576     }
577 
578     private void validateSubjectTypesSupported( )
579     {
580         Objects.requireNonNull( _subjectTypesSupported, "Subject types supported is required" );
581         for ( String type : _subjectTypesSupported )
582         {
583             Objects.requireNonNull( type, "subject type must not be null" );
584         }
585     }
586 
587     private void validateTokenEndpointAuthSigningAlgValuesSupported( )
588     {
589         if ( _tokenEndpointAuthSigningAlgValuesSupported == null )
590         {
591             return;
592         }
593         if ( Arrays.stream( _tokenEndpointAuthSigningAlgValuesSupported ).anyMatch( alg -> "none".equals( alg ) ) )
594         {
595             throw new AppException( "The algorithm none MUST NOT be used for the token endpoint auth signing alg values supported" );
596         }
597     }
598 
599     private void validateEndSessionEndpoint( )
600     {
601         if ( _strEndSessionEndpoint == null )
602         {
603             return;
604         }
605         validateURI( _strEndSessionEndpoint, "end session endpoint" );
606     }
607 
608     private void validateIDTokenSigningAlgValuesSupported( )
609     {
610         Objects.requireNonNull( _idTokenSigningAlgValuesSupported, "ID Token signing alg values supported is required" );
611         if ( Arrays.stream( _idTokenSigningAlgValuesSupported ).noneMatch( alg -> "RS256".equals( alg ) ) )
612         {
613             throw new AppException( "The algorithm RS256 MUST be included in ID Token signing alg values supported" );
614         }
615     }
616 
617     private void validateURI( String strURI, String strFieldName )
618     {
619         validateURI( strURI, strFieldName, true, true );
620     }
621 
622     private void validateURI( String strURI, String strFieldName, boolean queryAllowed, boolean fragmentAllowed )
623     {
624         try
625         {
626             URI theURI = new URI( strURI );
627             if ( !theURI.getScheme( ).equals( "https" ) )
628             {
629                 throw new AppException( strFieldName + " must be a URL using the https scheme" );
630             }
631             if ( !queryAllowed && theURI.getQuery( ) != null )
632             {
633                 throw new AppException( strFieldName + " must be a URL with no query component" );
634             }
635             if ( !fragmentAllowed && theURI.getFragment( ) != null )
636             {
637                 throw new AppException( strFieldName + " must be a URL with no fragment  component" );
638             }
639         }
640         catch( URISyntaxException e )
641         {
642             throw new AppException( "Unable to validate URI <" + strURI + "> for field " + strFieldName + ": " + e.getMessage( ), e );
643         }
644     }
645 }