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 com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
022import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils;
023import com.nimbusds.oauth2.sdk.util.StringUtils;
024import net.jcip.annotations.Immutable;
025
026import java.net.URI;
027import java.net.URISyntaxException;
028import java.util.*;
029
030
031/**
032 * Authorisation code grant. Used in access token requests with an
033 * authorisation code.
034 *
035 * <p>Related specifications:
036 *
037 * <ul>
038 *     <li>OAuth 2.0 (RFC 6749)
039 *     <li>Proof Key for Code Exchange by OAuth Public Clients (RFC 7636)
040 * </ul>
041 */
042@Immutable
043public class AuthorizationCodeGrant extends AuthorizationGrant {
044
045
046        /**
047         * The grant type.
048         */
049        public static final GrantType GRANT_TYPE = GrantType.AUTHORIZATION_CODE;
050
051
052        /**
053         * The authorisation code received from the authorisation server.
054         */
055        private final AuthorizationCode code;
056
057
058        /**
059         * The conditionally required redirection URI in the initial
060         * authorisation request.
061         */
062        private final URI redirectURI;
063
064
065        /**
066         * The optional authorisation code verifier for PKCE.
067         */
068        private final CodeVerifier codeVerifier;
069
070
071        /**
072         * Creates a new authorisation code grant.
073         *
074         * @param code        The authorisation code. Must not be {@code null}.
075         * @param redirectURI The redirection URI of the original authorisation
076         *                    request. Required if the {redirect_uri}
077         *                    parameter was included in the authorisation
078         *                    request, else {@code null}.
079         */
080        public AuthorizationCodeGrant(final AuthorizationCode code,
081                                      final URI redirectURI) {
082
083                this(code, redirectURI, null);
084        }
085
086
087        /**
088         * Creates a new authorisation code grant.
089         *
090         * @param code         The authorisation code. Must not be {@code null}.
091         * @param redirectURI  The redirection URI of the original
092         *                     authorisation request. Required if the
093         *                     {redirect_uri} parameter was included in the
094         *                     authorisation request, else {@code null}.
095         * @param codeVerifier The authorisation code verifier for PKCE,
096         *                     {@code null} if not specified.
097         */
098        public AuthorizationCodeGrant(final AuthorizationCode code,
099                                      final URI redirectURI,
100                                      final CodeVerifier codeVerifier) {
101
102                super(GRANT_TYPE);
103                this.code = Objects.requireNonNull(code);
104                this.redirectURI = redirectURI;
105                this.codeVerifier = codeVerifier;
106        }
107
108
109        /**
110         * Gets the authorisation code.
111         *
112         * @return The authorisation code.
113         */
114        public AuthorizationCode getAuthorizationCode() {
115
116                return code;
117        }
118
119
120        /**
121         * Gets the redirection URI of the original authorisation request.
122         *
123         * @return The redirection URI, {@code null} if the
124         *         {@code redirect_uri} parameter was not included in the
125         *         original authorisation request.
126         */
127        public URI getRedirectionURI() {
128
129                return redirectURI;
130        }
131
132
133        /**
134         * Gets the authorisation code verifier for PKCE.
135         *
136         * @return The authorisation code verifier, {@code null} if not
137         *         specified.
138         */
139        public CodeVerifier getCodeVerifier() {
140
141                return codeVerifier;
142        }
143
144
145        @Override
146        public Map<String,List<String>> toParameters() {
147
148                Map<String,List<String>> params = new LinkedHashMap<>();
149                params.put("grant_type", Collections.singletonList(GRANT_TYPE.getValue()));
150                params.put("code", Collections.singletonList(code.getValue()));
151
152                if (redirectURI != null)
153                        params.put("redirect_uri", Collections.singletonList(redirectURI.toString()));
154
155                if (codeVerifier != null)
156                        params.put("code_verifier", Collections.singletonList(codeVerifier.getValue()));
157
158                return params;
159        }
160        
161        
162        @Override
163        public boolean equals(Object o) {
164                if (this == o) return true;
165                if (!(o instanceof AuthorizationCodeGrant)) return false;
166                AuthorizationCodeGrant that = (AuthorizationCodeGrant) o;
167                return code.equals(that.code) && Objects.equals(redirectURI, that.redirectURI) && Objects.equals(getCodeVerifier(), that.getCodeVerifier());
168        }
169        
170        
171        @Override
172        public int hashCode() {
173                return Objects.hash(code, redirectURI, getCodeVerifier());
174        }
175        
176        
177        /**
178         * Parses an authorisation code grant from the specified request body
179         * parameters.
180         *
181         * <p>Example:
182         *
183         * <pre>
184         * grant_type=authorization_code
185         * code=SplxlOBeZQQYbYS6WxSbIA
186         * redirect_uri=https://Fclient.example.com/cb
187         * </pre>
188         *
189         * @param params The parameters.
190         *
191         * @return The authorisation code grant.
192         *
193         * @throws ParseException If parsing failed.
194         */
195        public static AuthorizationCodeGrant parse(final Map<String,List<String>> params)
196                throws ParseException {
197                
198                GrantType.ensure(GRANT_TYPE, params);
199
200                // Parse authorisation code
201                String codeString = MultivaluedMapUtils.getFirstValue(params, "code");
202
203                if (codeString == null || codeString.trim().isEmpty()) {
204                        String msg = "Missing or empty code parameter";
205                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
206                }
207
208                AuthorizationCode code = new AuthorizationCode(codeString);
209
210                // Parse optional redirection URI
211                String redirectURIString = MultivaluedMapUtils.getFirstValue(params, "redirect_uri");
212
213                URI redirectURI = null;
214
215                if (redirectURIString != null) {
216                        try {
217                                redirectURI = new URI(redirectURIString);
218                        } catch (URISyntaxException e) {
219                                String msg = "Invalid redirect_uri parameter: " + e.getMessage();
220                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), e);
221                        }
222                }
223
224
225                // Parse optional code verifier
226                String codeVerifierString = MultivaluedMapUtils.getFirstValue(params,"code_verifier");
227
228                CodeVerifier codeVerifier = null;
229
230                if (StringUtils.isNotBlank(codeVerifierString)) {
231
232                        try {
233                                codeVerifier = new CodeVerifier(codeVerifierString);
234                        } catch (IllegalArgumentException e) {
235                                // Illegal code verifier
236                                String msg = "Illegal code verifier: " + e.getMessage();
237                                throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), e);
238                        }
239                }
240
241                return new AuthorizationCodeGrant(code, redirectURI, codeVerifier);
242        }
243}