001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk;
019
020
021import java.net.MalformedURLException;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.net.URL;
025import java.util.*;
026
027import com.nimbusds.oauth2.sdk.http.HTTPRequest;
028import com.nimbusds.oauth2.sdk.id.ClientID;
029import com.nimbusds.oauth2.sdk.id.State;
030import com.nimbusds.oauth2.sdk.pkce.CodeChallenge;
031import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;
032import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
033import com.nimbusds.oauth2.sdk.util.URIUtils;
034import com.nimbusds.oauth2.sdk.util.URLUtils;
035import net.jcip.annotations.Immutable;
036import org.apache.commons.collections4.MapUtils;
037import org.apache.commons.lang3.StringUtils;
038
039
040/**
041 * Authorisation request. Used to authenticate an end-user and request the
042 * end-user's consent to grant the client access to a protected resource.
043 * Supports custom request parameters.
044 *
045 * <p>Extending classes may define additional request parameters as well as 
046 * enforce tighter requirements on the base parameters.
047 *
048 * <p>Example HTTP request:
049 *
050 * <pre>
051 * https://server.example.com/authorize?
052 * response_type=code
053 * &amp;client_id=s6BhdRkqt3
054 * &amp;state=xyz
055 * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
056 * </pre>
057 *
058 * <p>Related specifications:
059 *
060 * <ul>
061 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.1 and 4.2.1.
062 *     <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0.
063 *     <li>OAuth 2.0 Form Post Response Mode 1.0.
064 *     <li>Proof Key for Code Exchange by OAuth Public Clients (RFC 7636).
065 * </ul>
066 */
067@Immutable
068public class AuthorizationRequest extends AbstractRequest {
069
070
071        /**
072         * The registered parameter names.
073         */
074        private static final Set<String> REGISTERED_PARAMETER_NAMES;
075
076
077        /**
078         * Initialises the registered parameter name set.
079         */
080        static {
081                Set<String> p = new HashSet<>();
082
083                p.add("response_type");
084                p.add("client_id");
085                p.add("redirect_uri");
086                p.add("scope");
087                p.add("state");
088                p.add("response_mode");
089                p.add("code_challenge");
090                p.add("code_challenge_method");
091
092                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
093        }
094
095
096        /**
097         * The response type (required).
098         */
099        private final ResponseType rt;
100
101
102        /**
103         * The client identifier (required).
104         */
105        private final ClientID clientID;
106
107
108        /**
109         * The redirection URI where the response will be sent (optional). 
110         */
111        private final URI redirectURI;
112        
113        
114        /**
115         * The scope (optional).
116         */
117        private final Scope scope;
118        
119        
120        /**
121         * The opaque value to maintain state between the request and the 
122         * callback (recommended).
123         */
124        private final State state;
125
126
127        /**
128         * The response mode (optional).
129         */
130        private final ResponseMode rm;
131
132
133        /**
134         * The authorisation code challenge for PKCE (optional).
135         */
136        private final CodeChallenge codeChallenge;
137
138
139        /**
140         * The authorisation code challenge method for PKCE (optional).
141         */
142        private final CodeChallengeMethod codeChallengeMethod;
143
144
145        /**
146         * Additional custom parameters.
147         */
148        private final Map<String,String> customParams;
149
150
151        /**
152         * Builder for constructing authorisation requests.
153         */
154        public static class Builder {
155
156
157                /**
158                 * The endpoint URI (optional).
159                 */
160                private URI uri;
161
162
163                /**
164                 * The response type (required).
165                 */
166                private final ResponseType rt;
167
168
169                /**
170                 * The client identifier (required).
171                 */
172                private final ClientID clientID;
173
174
175                /**
176                 * The redirection URI where the response will be sent
177                 * (optional).
178                 */
179                private URI redirectURI;
180
181
182                /**
183                 * The scope (optional).
184                 */
185                private Scope scope;
186
187
188                /**
189                 * The opaque value to maintain state between the request and
190                 * the callback (recommended).
191                 */
192                private State state;
193
194
195                /**
196                 * The response mode (optional).
197                 */
198                private ResponseMode rm;
199
200
201                /**
202                 * The authorisation code challenge for PKCE (optional).
203                 */
204                private CodeChallenge codeChallenge;
205
206
207                /**
208                 * The authorisation code challenge method for PKCE (optional).
209                 */
210                private CodeChallengeMethod codeChallengeMethod;
211
212
213                /**
214                 * The additional custom parameters.
215                 */
216                private Map<String,String> customParams = new HashMap<>();
217
218
219                /**
220                 * Creates a new authorisation request builder.
221                 *
222                 * @param rt       The response type. Corresponds to the
223                 *                 {@code response_type} parameter. Must not be
224                 *                 {@code null}.
225                 * @param clientID The client identifier. Corresponds to the
226                 *                 {@code client_id} parameter. Must not be
227                 *                 {@code null}.
228                 */
229                public Builder(final ResponseType rt, final ClientID clientID) {
230
231                        if (rt == null)
232                                throw new IllegalArgumentException("The response type must not be null");
233
234                        this.rt = rt;
235
236
237                        if (clientID == null)
238                                throw new IllegalArgumentException("The client ID must not be null");
239
240                        this.clientID = clientID;
241                }
242                
243                
244                /**
245                 * Creates a new authorisation request builder from the
246                 * specified request.
247                 *
248                 * @param request The authorisation request. Must not be
249                 *                {@code null}.
250                 */
251                public Builder(final AuthorizationRequest request) {
252                        
253                        uri = request.getEndpointURI();
254                        scope = request.scope;
255                        rt = request.getResponseType();
256                        clientID = request.getClientID();
257                        redirectURI = request.getRedirectionURI();
258                        state = request.getState();
259                        rm = request.getResponseMode();
260                        codeChallenge = request.getCodeChallenge();
261                        codeChallengeMethod = request.getCodeChallengeMethod();
262                        customParams.putAll(request.getCustomParameters());
263                }
264
265
266                /**
267                 * Sets the redirection URI. Corresponds to the optional
268                 * {@code redirection_uri} parameter.
269                 *
270                 * @param redirectURI The redirection URI, {@code null} if not
271                 *                    specified.
272                 *
273                 * @return This builder.
274                 */
275                public Builder redirectionURI(final URI redirectURI) {
276
277                        this.redirectURI = redirectURI;
278                        return this;
279                }
280
281
282                /**
283                 * Sets the scope. Corresponds to the optional {@code scope}
284                 * parameter.
285                 *
286                 * @param scope The scope, {@code null} if not specified.
287                 *
288                 * @return This builder.
289                 */
290                public Builder scope(final Scope scope) {
291
292                        this.scope = scope;
293                        return this;
294                }
295
296
297                /**
298                 * Sets the state. Corresponds to the recommended {@code state}
299                 * parameter.
300                 *
301                 * @param state The state, {@code null} if not specified.
302                 *
303                 * @return This builder.
304                 */
305                public Builder state(final State state) {
306
307                        this.state = state;
308                        return this;
309                }
310
311
312                /**
313                 * Sets the response mode. Corresponds to the optional
314                 * {@code response_mode} parameter. Use of this parameter is
315                 * not recommended unless a non-default response mode is
316                 * requested (e.g. form_post).
317                 *
318                 * @param rm The response mode, {@code null} if not specified.
319                 *
320                 * @return This builder.
321                 */
322                public Builder responseMode(final ResponseMode rm) {
323
324                        this.rm = rm;
325                        return this;
326                }
327
328
329                /**
330                 * Sets the code challenge for Proof Key for Code Exchange
331                 * (PKCE) by public OAuth clients.
332                 *
333                 * @param codeChallenge       The code challenge, {@code null}
334                 *                            if not specified.
335                 * @param codeChallengeMethod The code challenge method,
336                 *                            {@code null} if not specified.
337                 *
338                 * @return This builder.
339                 */
340                @Deprecated
341                public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) {
342
343                        this.codeChallenge = codeChallenge;
344                        this.codeChallengeMethod = codeChallengeMethod;
345                        return this;
346                }
347
348
349                /**
350                 * Sets the code challenge for Proof Key for Code Exchange
351                 * (PKCE) by public OAuth clients.
352                 *
353                 * @param codeVerifier        The code verifier to use to
354                 *                            compute the code challenge,
355                 *                            {@code null} if PKCE is not
356                 *                            specified.
357                 * @param codeChallengeMethod The code challenge method,
358                 *                            {@code null} if not specified.
359                 *                            Defaults to
360                 *                            {@link CodeChallengeMethod#PLAIN}
361                 *                            if a code verifier is specified.
362                 *
363                 * @return This builder.
364                 */
365                public Builder codeChallenge(final CodeVerifier codeVerifier, final CodeChallengeMethod codeChallengeMethod) {
366
367                        if (codeVerifier != null) {
368                                CodeChallengeMethod method = codeChallengeMethod != null ? codeChallengeMethod : CodeChallengeMethod.getDefault();
369                                this.codeChallenge = CodeChallenge.compute(method, codeVerifier);
370                                this.codeChallengeMethod = method;
371                        } else {
372                                this.codeChallenge = null;
373                                this.codeChallengeMethod = null;
374                        }
375                        return this;
376                }
377
378
379                /**
380                 * Sets the specified additional custom parameter.
381                 *
382                 * @param name  The parameter name. Must not be {@code null}.
383                 * @param value The parameter value, {@code null} if not
384                 *              specified.
385                 *
386                 * @return This builder.
387                 */
388                public Builder customParameter(final String name, final String value) {
389
390                        customParams.put(name, value);
391                        return this;
392                }
393
394
395                /**
396                 * Sets the URI of the endpoint (HTTP or HTTPS) for which the
397                 * request is intended.
398                 *
399                 * @param uri The endpoint URI, {@code null} if not specified.
400                 *
401                 * @return This builder.
402                 */
403                public Builder endpointURI(final URI uri) {
404
405                        this.uri = uri;
406                        return this;
407                }
408
409
410                /**
411                 * Builds a new authorisation request.
412                 *
413                 * @return The authorisation request.
414                 */
415                public AuthorizationRequest build() {
416
417                        return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, customParams);
418                }
419        }
420
421
422        /**
423         * Creates a new minimal authorisation request.
424         *
425         * @param uri      The URI of the authorisation endpoint. May be
426         *                 {@code null} if the {@link #toHTTPRequest} method
427         *                 will not be used.
428         * @param rt       The response type. Corresponds to the
429         *                 {@code response_type} parameter. Must not be
430         *                 {@code null}.
431         * @param clientID The client identifier. Corresponds to the
432         *                 {@code client_id} parameter. Must not be
433         *                 {@code null}.
434         */
435        public AuthorizationRequest(final URI uri,
436                                    final ResponseType rt,
437                                    final ClientID clientID) {
438
439                this(uri, rt, null, clientID, null, null, null, null, null);
440        }
441
442
443        /**
444         * Creates a new authorisation request.
445         *
446         * @param uri                 The URI of the authorisation endpoint.
447         *                            May be {@code null} if the
448         *                            {@link #toHTTPRequest} method will not be
449         *                            used.
450         * @param rt                  The response type. Corresponds to the
451         *                            {@code response_type} parameter. Must not
452         *                            be {@code null}.
453         * @param rm                  The response mode. Corresponds to the
454         *                            optional {@code response_mode} parameter.
455         *                            Use of this parameter is not recommended
456         *                            unless a non-default response mode is
457         *                            requested (e.g. form_post).
458         * @param clientID            The client identifier. Corresponds to the
459         *                            {@code client_id} parameter. Must not be
460         *                            {@code null}.
461         * @param redirectURI         The redirection URI. Corresponds to the
462         *                            optional {@code redirect_uri} parameter.
463         *                            {@code null} if not specified.
464         * @param scope               The request scope. Corresponds to the
465         *                            optional {@code scope} parameter.
466         *                            {@code null} if not specified.
467         * @param state               The state. Corresponds to the recommended
468         *                            {@code state} parameter. {@code null} if
469         *                            not specified.
470         */
471        public AuthorizationRequest(final URI uri,
472                                    final ResponseType rt,
473                                    final ResponseMode rm,
474                                    final ClientID clientID,
475                                    final URI redirectURI,
476                                    final Scope scope,
477                                    final State state) {
478
479                this(uri, rt, rm, clientID, redirectURI, scope, state, null, null);
480        }
481
482
483        /**
484         * Creates a new authorisation request with PKCE support.
485         *
486         * @param uri                 The URI of the authorisation endpoint.
487         *                            May be {@code null} if the
488         *                            {@link #toHTTPRequest} method will not be
489         *                            used.
490         * @param rt                  The response type. Corresponds to the
491         *                            {@code response_type} parameter. Must not
492         *                            be {@code null}.
493         * @param rm                  The response mode. Corresponds to the
494         *                            optional {@code response_mode} parameter.
495         *                            Use of this parameter is not recommended
496         *                            unless a non-default response mode is
497         *                            requested (e.g. form_post).
498         * @param clientID            The client identifier. Corresponds to the
499         *                            {@code client_id} parameter. Must not be
500         *                            {@code null}.
501         * @param redirectURI         The redirection URI. Corresponds to the
502         *                            optional {@code redirect_uri} parameter.
503         *                            {@code null} if not specified.
504         * @param scope               The request scope. Corresponds to the
505         *                            optional {@code scope} parameter.
506         *                            {@code null} if not specified.
507         * @param state               The state. Corresponds to the recommended
508         *                            {@code state} parameter. {@code null} if
509         *                            not specified.
510         * @param codeChallenge       The code challenge for PKCE, {@code null}
511         *                            if not specified.
512         * @param codeChallengeMethod The code challenge method for PKCE,
513         *                            {@code null} if not specified.
514         */
515        public AuthorizationRequest(final URI uri,
516                                    final ResponseType rt,
517                                    final ResponseMode rm,
518                                    final ClientID clientID,
519                                    final URI redirectURI,
520                                    final Scope scope,
521                                    final State state,
522                                    final CodeChallenge codeChallenge,
523                                    final CodeChallengeMethod codeChallengeMethod) {
524
525                this(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, Collections.<String,String>emptyMap());
526        }
527
528
529        /**
530         * Creates a new authorisation request with PKCE support and additional
531         * custom parameters.
532         *
533         * @param uri                 The URI of the authorisation endpoint.
534         *                            May be {@code null} if the
535         *                            {@link #toHTTPRequest} method will not be
536         *                            used.
537         * @param rt                  The response type. Corresponds to the
538         *                            {@code response_type} parameter. Must not
539         *                            be {@code null}.
540         * @param rm                  The response mode. Corresponds to the
541         *                            optional {@code response_mode} parameter.
542         *                            Use of this parameter is not recommended
543         *                            unless a non-default response mode is
544         *                            requested (e.g. form_post).
545         * @param clientID            The client identifier. Corresponds to the
546         *                            {@code client_id} parameter. Must not be
547         *                            {@code null}.
548         * @param redirectURI         The redirection URI. Corresponds to the
549         *                            optional {@code redirect_uri} parameter.
550         *                            {@code null} if not specified.
551         * @param scope               The request scope. Corresponds to the
552         *                            optional {@code scope} parameter.
553         *                            {@code null} if not specified.
554         * @param state               The state. Corresponds to the recommended
555         *                            {@code state} parameter. {@code null} if
556         *                            not specified.
557         * @param codeChallenge       The code challenge for PKCE, {@code null}
558         *                            if not specified.
559         * @param codeChallengeMethod The code challenge method for PKCE,
560         *                            {@code null} if not specified.
561         * @param customParams        Additional custom parameters, empty map
562         *                            or {@code null} if none.
563         */
564        public AuthorizationRequest(final URI uri,
565                                    final ResponseType rt,
566                                    final ResponseMode rm,
567                                    final ClientID clientID,
568                                    final URI redirectURI,
569                                    final Scope scope,
570                                    final State state,
571                                    final CodeChallenge codeChallenge,
572                                    final CodeChallengeMethod codeChallengeMethod,
573                                    final Map<String,String> customParams) {
574
575                super(uri);
576
577                if (rt == null)
578                        throw new IllegalArgumentException("The response type must not be null");
579
580                this.rt = rt;
581
582                this.rm = rm;
583
584
585                if (clientID == null)
586                        throw new IllegalArgumentException("The client ID must not be null");
587
588                this.clientID = clientID;
589
590
591                this.redirectURI = redirectURI;
592                this.scope = scope;
593                this.state = state;
594
595                this.codeChallenge = codeChallenge;
596                this.codeChallengeMethod = codeChallengeMethod;
597
598                if (MapUtils.isNotEmpty(customParams)) {
599                        this.customParams = Collections.unmodifiableMap(customParams);
600                } else {
601                        this.customParams = Collections.emptyMap();
602                }
603        }
604
605
606        /**
607         * Returns the registered (standard) OAuth 2.0 authorisation request
608         * parameter names.
609         *
610         * @return The registered OAuth 2.0 authorisation request parameter
611         *         names, as a unmodifiable set.
612         */
613        public static Set<String> getRegisteredParameterNames() {
614
615                return REGISTERED_PARAMETER_NAMES;
616        }
617
618
619        /**
620         * Gets the response type. Corresponds to the {@code response_type}
621         * parameter.
622         *
623         * @return The response type.
624         */
625        public ResponseType getResponseType() {
626        
627                return rt;
628        }
629
630
631        /**
632         * Gets the optional response mode. Corresponds to the optional
633         * {@code response_mode} parameter.
634         *
635         * @return The response mode, {@code null} if not specified.
636         */
637        public ResponseMode getResponseMode() {
638
639                return rm;
640        }
641
642
643        /**
644         * Returns the implied response mode, determined by the optional
645         * {@code response_mode} parameter, and if that isn't specified, by
646         * the {@code response_type}.
647         *
648         * @return The implied response mode.
649         */
650        public ResponseMode impliedResponseMode() {
651
652                if (rm != null) {
653                        return rm;
654                } else if (rt.impliesImplicitFlow()) {
655                        return ResponseMode.FRAGMENT;
656                } else {
657                        return ResponseMode.QUERY;
658                }
659        }
660
661
662        /**
663         * Gets the client identifier. Corresponds to the {@code client_id} 
664         * parameter.
665         *
666         * @return The client identifier.
667         */
668        public ClientID getClientID() {
669        
670                return clientID;
671        }
672
673
674        /**
675         * Gets the redirection URI. Corresponds to the optional 
676         * {@code redirection_uri} parameter.
677         *
678         * @return The redirection URI, {@code null} if not specified.
679         */
680        public URI getRedirectionURI() {
681        
682                return redirectURI;
683        }
684        
685        
686        /**
687         * Gets the scope. Corresponds to the optional {@code scope} parameter.
688         *
689         * @return The scope, {@code null} if not specified.
690         */
691        public Scope getScope() {
692        
693                return scope;
694        }
695        
696        
697        /**
698         * Gets the state. Corresponds to the recommended {@code state} 
699         * parameter.
700         *
701         * @return The state, {@code null} if not specified.
702         */
703        public State getState() {
704        
705                return state;
706        }
707
708
709        /**
710         * Returns the code challenge for PKCE.
711         *
712         * @return The code challenge, {@code null} if not specified.
713         */
714        public CodeChallenge getCodeChallenge() {
715
716                return codeChallenge;
717        }
718
719
720        /**
721         * Returns the code challenge method for PKCE.
722         *
723         * @return The code challenge method, {@code null} if not specified.
724         */
725        public CodeChallengeMethod getCodeChallengeMethod() {
726
727                return codeChallengeMethod;
728        }
729
730
731        /**
732         * Returns the additional custom parameters.
733         *
734         * @return The additional custom parameters as a unmodifiable map,
735         *         empty map if none.
736         */
737        public Map<String,String> getCustomParameters () {
738
739                return customParams;
740        }
741
742
743        /**
744         * Returns the specified custom parameter.
745         *
746         * @param name The parameter name. Must not be {@code null}.
747         *
748         * @return The parameter value, {@code null} if not specified.
749         */
750        public String getCustomParameter(final String name) {
751
752                return customParams.get(name);
753        }
754
755
756        /**
757         * Returns the parameters for this authorisation request.
758         *
759         * <p>Example parameters:
760         *
761         * <pre>
762         * response_type = code
763         * client_id     = s6BhdRkqt3
764         * state         = xyz
765         * redirect_uri  = https://client.example.com/cb
766         * </pre>
767         * 
768         * @return The parameters.
769         */
770        public Map<String,String> toParameters() {
771
772                Map <String,String> params = new LinkedHashMap<>();
773
774                // Put custom params first, so they may be overwritten by std params
775                params.putAll(customParams);
776                
777                params.put("response_type", rt.toString());
778                params.put("client_id", clientID.getValue());
779
780                if (rm != null) {
781                        params.put("response_mode", rm.getValue());
782                }
783
784                if (redirectURI != null)
785                        params.put("redirect_uri", redirectURI.toString());
786
787                if (scope != null)
788                        params.put("scope", scope.toString());
789                
790                if (state != null)
791                        params.put("state", state.getValue());
792
793                if (codeChallenge != null) {
794                        params.put("code_challenge", codeChallenge.getValue());
795
796                        if (codeChallengeMethod != null) {
797                                params.put("code_challenge_method", codeChallengeMethod.getValue());
798                        }
799                }
800
801                return params;
802        }
803        
804        
805        /**
806         * Returns the URI query string for this authorisation request.
807         *
808         * <p>Note that the '?' character preceding the query string in an URI
809         * is not included in the returned string.
810         *
811         * <p>Example URI query string:
812         *
813         * <pre>
814         * response_type=code
815         * &amp;client_id=s6BhdRkqt3
816         * &amp;state=xyz
817         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
818         * </pre>
819         * 
820         * @return The URI query string.
821         */
822        public String toQueryString() {
823                
824                return URLUtils.serializeParameters(toParameters());
825        }
826
827
828        /**
829         * Returns the complete URI representation for this authorisation
830         * request, consisting of the {@link #getEndpointURI authorization
831         * endpoint URI} with the {@link #toQueryString query string} appended.
832         *
833         * <p>Example URI:
834         *
835         * <pre>
836         * https://server.example.com/authorize?
837         * response_type=code
838         * &amp;client_id=s6BhdRkqt3
839         * &amp;state=xyz
840         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
841         * </pre>
842         *
843         * @return The URI representation.
844         */
845        public URI toURI() {
846
847                if (getEndpointURI() == null)
848                        throw new SerializeException("The authorization endpoint URI is not specified");
849
850                StringBuilder sb = new StringBuilder(getEndpointURI().toString());
851                sb.append('?');
852                sb.append(toQueryString());
853                try {
854                        return new URI(sb.toString());
855                } catch (URISyntaxException e) {
856                        throw new SerializeException("Couldn't append query string: " + e.getMessage(), e);
857                }
858        }
859        
860        
861        /**
862         * Returns the matching HTTP request.
863         *
864         * @param method The HTTP request method which can be GET or POST. Must
865         *               not be {@code null}.
866         *
867         * @return The HTTP request.
868         */
869        public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) {
870                
871                if (getEndpointURI() == null)
872                        throw new SerializeException("The endpoint URI is not specified");
873                
874                HTTPRequest httpRequest;
875
876                URL endpointURL;
877
878                try {
879                        endpointURL = getEndpointURI().toURL();
880
881                } catch (MalformedURLException e) {
882
883                        throw new SerializeException(e.getMessage(), e);
884                }
885                
886                if (method.equals(HTTPRequest.Method.GET)) {
887
888                        httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL);
889
890                } else if (method.equals(HTTPRequest.Method.POST)) {
891
892                        httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL);
893
894                } else {
895
896                        throw new IllegalArgumentException("The HTTP request method must be GET or POST");
897                }
898                
899                httpRequest.setQuery(toQueryString());
900                
901                return httpRequest;
902        }
903        
904        
905        @Override
906        public HTTPRequest toHTTPRequest() {
907        
908                return toHTTPRequest(HTTPRequest.Method.GET);
909        }
910
911
912        /**
913         * Parses an authorisation request from the specified parameters.
914         *
915         * <p>Example parameters:
916         *
917         * <pre>
918         * response_type = code
919         * client_id     = s6BhdRkqt3
920         * state         = xyz
921         * redirect_uri  = https://client.example.com/cb
922         * </pre>
923         *
924         * @param params The parameters. Must not be {@code null}.
925         *
926         * @return The authorisation request.
927         *
928         * @throws ParseException If the parameters couldn't be parsed to an
929         *                        authorisation request.
930         */
931        public static AuthorizationRequest parse(final Map<String,String> params)
932                throws ParseException {
933
934                return parse(null, params);
935        }
936
937
938        /**
939         * Parses an authorisation request from the specified parameters.
940         *
941         * <p>Example parameters:
942         *
943         * <pre>
944         * response_type = code
945         * client_id     = s6BhdRkqt3
946         * state         = xyz
947         * redirect_uri  = https://client.example.com/cb
948         * </pre>
949         *
950         * @param uri    The URI of the authorisation endpoint. May be
951         *               {@code null} if the {@link #toHTTPRequest()} method
952         *               will not be used.
953         * @param params The parameters. Must not be {@code null}.
954         *
955         * @return The authorisation request.
956         *
957         * @throws ParseException If the parameters couldn't be parsed to an
958         *                        authorisation request.
959         */
960        public static AuthorizationRequest parse(final URI uri, final Map<String,String> params)
961                throws ParseException {
962
963                // Parse mandatory client ID first
964                String v = params.get("client_id");
965
966                if (StringUtils.isBlank(v)) {
967                        String msg = "Missing \"client_id\" parameter";
968                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
969                }
970
971                ClientID clientID = new ClientID(v);
972
973
974                // Parse optional redirection URI second
975                v = params.get("redirect_uri");
976
977                URI redirectURI = null;
978
979                if (StringUtils.isNotBlank(v)) {
980
981                        try {
982                                redirectURI = new URI(v);
983
984                        } catch (URISyntaxException e) {
985                                String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage();
986                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
987                                                         clientID, null, null, null, e);
988                        }
989                }
990
991
992                // Parse optional state third
993                State state = State.parse(params.get("state"));
994
995
996                // Parse mandatory response type
997                v = params.get("response_type");
998
999                ResponseType rt;
1000
1001                try {
1002                        rt = ResponseType.parse(v);
1003
1004                } catch (ParseException e) {
1005                        // Only cause
1006                        String msg = "Missing \"response_type\" parameter";
1007                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1008                                                 clientID, redirectURI, null, state, e);
1009                }
1010
1011
1012                // Parse the optional response mode
1013                v = params.get("response_mode");
1014
1015                ResponseMode rm = null;
1016
1017                if (StringUtils.isNotBlank(v)) {
1018                        rm = new ResponseMode(v);
1019                }
1020
1021
1022                // Parse optional scope
1023                v = params.get("scope");
1024
1025                Scope scope = null;
1026
1027                if (StringUtils.isNotBlank(v))
1028                        scope = Scope.parse(v);
1029
1030
1031                // Parse optional code challenge and method for PKCE
1032                CodeChallenge codeChallenge = null;
1033                CodeChallengeMethod codeChallengeMethod = null;
1034
1035                v = params.get("code_challenge");
1036
1037                if (StringUtils.isNotBlank(v))
1038                        codeChallenge = CodeChallenge.parse(v);
1039
1040                if (codeChallenge != null) {
1041
1042                        v = params.get("code_challenge_method");
1043
1044                        if (StringUtils.isNotBlank(v))
1045                                codeChallengeMethod = CodeChallengeMethod.parse(v);
1046                }
1047
1048                // Parse additional custom parameters
1049                Map<String,String> customParams = null;
1050
1051                for (Map.Entry<String,String> p: params.entrySet()) {
1052
1053                        if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) {
1054                                // We have a custom parameter
1055                                if (customParams == null) {
1056                                        customParams = new HashMap<>();
1057                                }
1058                                customParams.put(p.getKey(), p.getValue());
1059                        }
1060                }
1061
1062
1063                return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, customParams);
1064        }
1065
1066
1067        /**
1068         * Parses an authorisation request from the specified URI query string.
1069         *
1070         * <p>Example URI query string:
1071         *
1072         * <pre>
1073         * response_type=code
1074         * &amp;client_id=s6BhdRkqt3
1075         * &amp;state=xyz
1076         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1077         * </pre>
1078         *
1079         * @param query The URI query string. Must not be {@code null}.
1080         *
1081         * @return The authorisation request.
1082         *
1083         * @throws ParseException If the query string couldn't be parsed to an
1084         *                        authorisation request.
1085         */
1086        public static AuthorizationRequest parse(final String query)
1087                throws ParseException {
1088
1089                return parse(null, URLUtils.parseParameters(query));
1090        }
1091        
1092        
1093        /**
1094         * Parses an authorisation request from the specified URI query string.
1095         *
1096         * <p>Example URI query string:
1097         *
1098         * <pre>
1099         * response_type=code
1100         * &amp;client_id=s6BhdRkqt3
1101         * &amp;state=xyz
1102         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1103         * </pre>
1104         *
1105         * @param uri   The URI of the authorisation endpoint. May be 
1106         *              {@code null} if the {@link #toHTTPRequest()} method
1107         *              will not be used.
1108         * @param query The URI query string. Must not be {@code null}.
1109         *
1110         * @return The authorisation request.
1111         *
1112         * @throws ParseException If the query string couldn't be parsed to an 
1113         *                        authorisation request.
1114         */
1115        public static AuthorizationRequest parse(final URI uri, final String query)
1116                throws ParseException {
1117        
1118                return parse(uri, URLUtils.parseParameters(query));
1119        }
1120
1121
1122        /**
1123         * Parses an authorisation request from the specified URI.
1124         *
1125         * <p>Example URI:
1126         *
1127         * <pre>
1128         * https://server.example.com/authorize?
1129         * response_type=code
1130         * &amp;client_id=s6BhdRkqt3
1131         * &amp;state=xyz
1132         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1133         * </pre>
1134         *
1135         * @param uri The URI. Must not be {@code null}.
1136         *
1137         * @return The authorisation request.
1138         *
1139         * @throws ParseException If the URI couldn't be parsed to an
1140         *                        authorisation request.
1141         */
1142        public static AuthorizationRequest parse(final URI uri)
1143                throws ParseException {
1144
1145                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery()));
1146        }
1147        
1148        
1149        /**
1150         * Parses an authorisation request from the specified HTTP request.
1151         *
1152         * <p>Example HTTP request (GET):
1153         *
1154         * <pre>
1155         * https://server.example.com/authorize?
1156         * response_type=code
1157         * &amp;client_id=s6BhdRkqt3
1158         * &amp;state=xyz
1159         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1160         * </pre>
1161         *
1162         * @param httpRequest The HTTP request. Must not be {@code null}.
1163         *
1164         * @return The authorisation request.
1165         *
1166         * @throws ParseException If the HTTP request couldn't be parsed to an 
1167         *                        authorisation request.
1168         */
1169        public static AuthorizationRequest parse(final HTTPRequest httpRequest) 
1170                throws ParseException {
1171                
1172                String query = httpRequest.getQuery();
1173                
1174                if (query == null)
1175                        throw new ParseException("Missing URI query string");
1176
1177                try {
1178                        return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query);
1179
1180                } catch (URISyntaxException e) {
1181
1182                        throw new ParseException(e.getMessage(), e);
1183                }
1184        }
1185}