1616
1717package org .springframework .security .oauth2 .core .endpoint ;
1818
19+ import java .io .Serial ;
1920import java .io .Serializable ;
2021import java .net .URI ;
2122import java .nio .charset .StandardCharsets ;
5152 * "https://tools.ietf.org/html/rfc6749#section-4.1.1">Section 4.1.1 Authorization Code
5253 * Grant Request</a>
5354 */
54- public final class OAuth2AuthorizationRequest implements Serializable {
55+ public class OAuth2AuthorizationRequest implements Serializable {
5556
57+ @ Serial
5658 private static final long serialVersionUID = 620L ;
5759
58- private String authorizationUri ;
60+ private final String authorizationUri ;
5961
60- private AuthorizationGrantType authorizationGrantType ;
62+ private final AuthorizationGrantType authorizationGrantType ;
6163
62- private OAuth2AuthorizationResponseType responseType ;
64+ private final OAuth2AuthorizationResponseType responseType ;
6365
64- private String clientId ;
66+ private final String clientId ;
6567
66- private String redirectUri ;
68+ private final String redirectUri ;
6769
68- private Set <String > scopes ;
70+ private final Set <String > scopes ;
6971
70- private String state ;
72+ private final String state ;
7173
72- private Map <String , Object > additionalParameters ;
74+ private final Map <String , Object > additionalParameters ;
7375
74- private String authorizationRequestUri ;
76+ private final String authorizationRequestUri ;
7577
76- private Map <String , Object > attributes ;
78+ private final Map <String , Object > attributes ;
7779
78- private OAuth2AuthorizationRequest () {
80+ protected OAuth2AuthorizationRequest (AbstractBuilder <?, ?> builder ) {
81+ Assert .hasText (builder .authorizationUri , "authorizationUri cannot be empty" );
82+ Assert .hasText (builder .clientId , "clientId cannot be empty" );
83+ this .authorizationUri = builder .authorizationUri ;
84+ this .authorizationGrantType = builder .authorizationGrantType ;
85+ this .responseType = builder .responseType ;
86+ this .clientId = builder .clientId ;
87+ this .redirectUri = builder .redirectUri ;
88+ this .scopes = Collections .unmodifiableSet (
89+ CollectionUtils .isEmpty (builder .scopes ) ? Collections .emptySet () : new LinkedHashSet <>(builder .scopes ));
90+ this .state = builder .state ;
91+ this .additionalParameters = Collections .unmodifiableMap (builder .additionalParameters );
92+ this .authorizationRequestUri = StringUtils .hasText (builder .authorizationRequestUri )
93+ ? builder .authorizationRequestUri : builder .buildAuthorizationRequestUri ();
94+ this .attributes = Collections .unmodifiableMap (builder .attributes );
7995 }
8096
8197 /**
@@ -185,7 +201,7 @@ public String getAuthorizationRequestUri() {
185201 * @return the {@link Builder}
186202 */
187203 public static Builder authorizationCode () {
188- return new Builder (AuthorizationGrantType . AUTHORIZATION_CODE );
204+ return new Builder ();
189205 }
190206
191207 @ Override
@@ -226,7 +242,7 @@ public int hashCode() {
226242 public static Builder from (OAuth2AuthorizationRequest authorizationRequest ) {
227243 Assert .notNull (authorizationRequest , "authorizationRequest cannot be null" );
228244 // @formatter:off
229- return new Builder (authorizationRequest . getGrantType () )
245+ return new Builder ()
230246 .authorizationUri (authorizationRequest .getAuthorizationUri ())
231247 .clientId (authorizationRequest .getClientId ())
232248 .redirectUri (authorizationRequest .getRedirectUri ())
@@ -240,13 +256,32 @@ public static Builder from(OAuth2AuthorizationRequest authorizationRequest) {
240256 /**
241257 * A builder for {@link OAuth2AuthorizationRequest}.
242258 */
243- public static final class Builder {
259+ public static class Builder extends AbstractBuilder <OAuth2AuthorizationRequest , Builder > {
260+
261+ /**
262+ * Builds a new {@link OAuth2AuthorizationRequest}.
263+ * @return a {@link OAuth2AuthorizationRequest}
264+ */
265+ @ Override
266+ public OAuth2AuthorizationRequest build () {
267+ return new OAuth2AuthorizationRequest (this );
268+ }
269+
270+ }
271+
272+ /**
273+ * A builder for subclasses of {@link OAuth2AuthorizationRequest}.
274+ *
275+ * @param <T> the type of authorization request
276+ * @param <B> the type of the builder
277+ */
278+ protected abstract static class AbstractBuilder <T extends OAuth2AuthorizationRequest , B extends AbstractBuilder <T , B >> {
244279
245280 private String authorizationUri ;
246281
247- private AuthorizationGrantType authorizationGrantType ;
282+ private final AuthorizationGrantType authorizationGrantType = AuthorizationGrantType . AUTHORIZATION_CODE ;
248283
249- private OAuth2AuthorizationResponseType responseType ;
284+ private final OAuth2AuthorizationResponseType responseType = OAuth2AuthorizationResponseType . CODE ;
250285
251286 private String clientId ;
252287
@@ -269,144 +304,149 @@ public static final class Builder {
269304
270305 private final DefaultUriBuilderFactory uriBuilderFactory ;
271306
272- private Builder (AuthorizationGrantType authorizationGrantType ) {
273- Assert .notNull (authorizationGrantType , "authorizationGrantType cannot be null" );
274- this .authorizationGrantType = authorizationGrantType ;
275- if (AuthorizationGrantType .AUTHORIZATION_CODE .equals (authorizationGrantType )) {
276- this .responseType = OAuth2AuthorizationResponseType .CODE ;
277- }
307+ protected AbstractBuilder () {
278308 this .uriBuilderFactory = new DefaultUriBuilderFactory ();
279309 // The supplied authorizationUri may contain encoded parameters
280310 // so disable encoding in UriBuilder and instead apply encoding within this
281311 // builder
282312 this .uriBuilderFactory .setEncodingMode (DefaultUriBuilderFactory .EncodingMode .NONE );
283313 }
284314
315+ @ SuppressWarnings ("unchecked" )
316+ protected final B getThis () {
317+ // avoid unchecked casts in subclasses by using "getThis()" instead of "(B)
318+ // this"
319+ return (B ) this ;
320+ }
321+
285322 /**
286323 * Sets the uri for the authorization endpoint.
287324 * @param authorizationUri the uri for the authorization endpoint
288- * @return the {@link Builder }
325+ * @return the {@link AbstractBuilder }
289326 */
290- public Builder authorizationUri (String authorizationUri ) {
327+ public B authorizationUri (String authorizationUri ) {
291328 this .authorizationUri = authorizationUri ;
292- return this ;
329+ return getThis () ;
293330 }
294331
295332 /**
296333 * Sets the client identifier.
297334 * @param clientId the client identifier
298- * @return the {@link Builder }
335+ * @return the {@link AbstractBuilder }
299336 */
300- public Builder clientId (String clientId ) {
337+ public B clientId (String clientId ) {
301338 this .clientId = clientId ;
302- return this ;
339+ return getThis () ;
303340 }
304341
305342 /**
306343 * Sets the uri for the redirection endpoint.
307344 * @param redirectUri the uri for the redirection endpoint
308- * @return the {@link Builder }
345+ * @return the {@link AbstractBuilder }
309346 */
310- public Builder redirectUri (String redirectUri ) {
347+ public B redirectUri (String redirectUri ) {
311348 this .redirectUri = redirectUri ;
312- return this ;
349+ return getThis () ;
313350 }
314351
315352 /**
316353 * Sets the scope(s).
317354 * @param scope the scope(s)
318- * @return the {@link Builder }
355+ * @return the {@link AbstractBuilder }
319356 */
320- public Builder scope (String ... scope ) {
357+ public B scope (String ... scope ) {
321358 if (scope != null && scope .length > 0 ) {
322359 return scopes (new LinkedHashSet <>(Arrays .asList (scope )));
323360 }
324- return this ;
361+ return getThis () ;
325362 }
326363
327364 /**
328365 * Sets the scope(s).
329366 * @param scopes the scope(s)
330- * @return the {@link Builder }
367+ * @return the {@link AbstractBuilder }
331368 */
332- public Builder scopes (Set <String > scopes ) {
369+ public B scopes (Set <String > scopes ) {
333370 this .scopes = scopes ;
334- return this ;
371+ return getThis () ;
335372 }
336373
337374 /**
338375 * Sets the state.
339376 * @param state the state
340- * @return the {@link Builder }
377+ * @return the {@link AbstractBuilder }
341378 */
342- public Builder state (String state ) {
379+ public B state (String state ) {
343380 this .state = state ;
344- return this ;
381+ return getThis () ;
345382 }
346383
347384 /**
348385 * Sets the additional parameter(s) used in the request.
349386 * @param additionalParameters the additional parameter(s) used in the request
350- * @return the {@link Builder }
387+ * @return the {@link AbstractBuilder }
351388 */
352- public Builder additionalParameters (Map <String , Object > additionalParameters ) {
389+ public B additionalParameters (Map <String , Object > additionalParameters ) {
353390 if (!CollectionUtils .isEmpty (additionalParameters )) {
354391 this .additionalParameters .putAll (additionalParameters );
355392 }
356- return this ;
393+ return getThis () ;
357394 }
358395
359396 /**
360397 * A {@code Consumer} to be provided access to the additional parameter(s)
361398 * allowing the ability to add, replace, or remove.
362399 * @param additionalParametersConsumer a {@code Consumer} of the additional
363400 * parameters
401+ * @return the {@link AbstractBuilder}
364402 * @since 5.3
365403 */
366- public Builder additionalParameters (Consumer <Map <String , Object >> additionalParametersConsumer ) {
404+ public B additionalParameters (Consumer <Map <String , Object >> additionalParametersConsumer ) {
367405 if (additionalParametersConsumer != null ) {
368406 additionalParametersConsumer .accept (this .additionalParameters );
369407 }
370- return this ;
408+ return getThis () ;
371409 }
372410
373411 /**
374412 * A {@code Consumer} to be provided access to all the parameters allowing the
375413 * ability to add, replace, or remove.
376414 * @param parametersConsumer a {@code Consumer} of all the parameters
415+ * @return the {@link AbstractBuilder}
377416 * @since 5.3
378417 */
379- public Builder parameters (Consumer <Map <String , Object >> parametersConsumer ) {
418+ public B parameters (Consumer <Map <String , Object >> parametersConsumer ) {
380419 if (parametersConsumer != null ) {
381420 this .parametersConsumer = parametersConsumer ;
382421 }
383- return this ;
422+ return getThis () ;
384423 }
385424
386425 /**
387426 * Sets the attributes associated to the request.
388427 * @param attributes the attributes associated to the request
389- * @return the {@link Builder }
428+ * @return the {@link AbstractBuilder }
390429 * @since 5.2
391430 */
392- public Builder attributes (Map <String , Object > attributes ) {
431+ public B attributes (Map <String , Object > attributes ) {
393432 if (!CollectionUtils .isEmpty (attributes )) {
394433 this .attributes .putAll (attributes );
395434 }
396- return this ;
435+ return getThis () ;
397436 }
398437
399438 /**
400439 * A {@code Consumer} to be provided access to the attribute(s) allowing the
401440 * ability to add, replace, or remove.
402441 * @param attributesConsumer a {@code Consumer} of the attribute(s)
442+ * @return the {@link AbstractBuilder}
403443 * @since 5.3
404444 */
405- public Builder attributes (Consumer <Map <String , Object >> attributesConsumer ) {
445+ public B attributes (Consumer <Map <String , Object >> attributesConsumer ) {
406446 if (attributesConsumer != null ) {
407447 attributesConsumer .accept (this .attributes );
408448 }
409- return this ;
449+ return getThis () ;
410450 }
411451
412452 /**
@@ -418,50 +458,30 @@ public Builder attributes(Consumer<Map<String, Object>> attributesConsumer) {
418458 * {@code application/x-www-form-urlencoded} MIME format.
419459 * @param authorizationRequestUri the {@code URI} string representation of the
420460 * OAuth 2.0 Authorization Request
421- * @return the {@link Builder }
461+ * @return the {@link AbstractBuilder }
422462 * @since 5.1
423463 */
424- public Builder authorizationRequestUri (String authorizationRequestUri ) {
464+ public B authorizationRequestUri (String authorizationRequestUri ) {
425465 this .authorizationRequestUri = authorizationRequestUri ;
426- return this ;
466+ return getThis () ;
427467 }
428468
429469 /**
430470 * A {@code Function} to be provided a {@code UriBuilder} representation of the
431471 * OAuth 2.0 Authorization Request allowing for further customizations.
432472 * @param authorizationRequestUriFunction a {@code Function} to be provided a
433473 * {@code UriBuilder} representation of the OAuth 2.0 Authorization Request
474+ * @return the {@link AbstractBuilder}
434475 * @since 5.3
435476 */
436- public Builder authorizationRequestUri (Function <UriBuilder , URI > authorizationRequestUriFunction ) {
477+ public B authorizationRequestUri (Function <UriBuilder , URI > authorizationRequestUriFunction ) {
437478 if (authorizationRequestUriFunction != null ) {
438479 this .authorizationRequestUriFunction = authorizationRequestUriFunction ;
439480 }
440- return this ;
481+ return getThis () ;
441482 }
442483
443- /**
444- * Builds a new {@link OAuth2AuthorizationRequest}.
445- * @return a {@link OAuth2AuthorizationRequest}
446- */
447- public OAuth2AuthorizationRequest build () {
448- Assert .hasText (this .authorizationUri , "authorizationUri cannot be empty" );
449- Assert .hasText (this .clientId , "clientId cannot be empty" );
450- OAuth2AuthorizationRequest authorizationRequest = new OAuth2AuthorizationRequest ();
451- authorizationRequest .authorizationUri = this .authorizationUri ;
452- authorizationRequest .authorizationGrantType = this .authorizationGrantType ;
453- authorizationRequest .responseType = this .responseType ;
454- authorizationRequest .clientId = this .clientId ;
455- authorizationRequest .redirectUri = this .redirectUri ;
456- authorizationRequest .state = this .state ;
457- authorizationRequest .scopes = Collections .unmodifiableSet (
458- CollectionUtils .isEmpty (this .scopes ) ? Collections .emptySet () : new LinkedHashSet <>(this .scopes ));
459- authorizationRequest .additionalParameters = Collections .unmodifiableMap (this .additionalParameters );
460- authorizationRequest .attributes = Collections .unmodifiableMap (this .attributes );
461- authorizationRequest .authorizationRequestUri = StringUtils .hasText (this .authorizationRequestUri )
462- ? this .authorizationRequestUri : this .buildAuthorizationRequestUri ();
463- return authorizationRequest ;
464- }
484+ public abstract T build ();
465485
466486 private String buildAuthorizationRequestUri () {
467487 Map <String , Object > parameters = getParameters (); // Not encoded
@@ -486,7 +506,7 @@ else if (v != null && v.getClass().isArray()) {
486506 return this .authorizationRequestUriFunction .apply (uriBuilder ).toString ();
487507 }
488508
489- private Map <String , Object > getParameters () {
509+ protected Map <String , Object > getParameters () {
490510 Map <String , Object > parameters = new LinkedHashMap <>();
491511 parameters .put (OAuth2ParameterNames .RESPONSE_TYPE , this .responseType .getValue ());
492512 parameters .put (OAuth2ParameterNames .CLIENT_ID , this .clientId );
0 commit comments