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