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.URI;
022import java.net.URISyntaxException;
023import java.util.LinkedHashMap;
024import java.util.Map;
025
026import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
027import net.jcip.annotations.Immutable;
028import org.apache.commons.lang3.StringUtils;
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), section 4.1.3.
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
104                if (code == null)
105                        throw new IllegalArgumentException("The authorisation code must not be null");
106
107                this.code = code;
108
109                this.redirectURI = redirectURI;
110
111                this.codeVerifier = codeVerifier;
112        }
113
114
115        /**
116         * Gets the authorisation code.
117         *
118         * @return The authorisation code.
119         */
120        public AuthorizationCode getAuthorizationCode() {
121
122                return code;
123        }
124
125
126        /**
127         * Gets the redirection URI of the original authorisation request.
128         *
129         * @return The redirection URI, {@code null} if the
130         *         {@code redirect_uri} parameter was not included in the
131         *         original authorisation request.
132         */
133        public URI getRedirectionURI() {
134
135                return redirectURI;
136        }
137
138
139        /**
140         * Gets the authorisation code verifier for PKCE.
141         *
142         * @return The authorisation code verifier, {@code null} if not
143         *         specified.
144         */
145        public CodeVerifier getCodeVerifier() {
146
147                return codeVerifier;
148        }
149
150
151        @Override
152        public Map<String,String> toParameters() {
153
154                Map<String,String> params = new LinkedHashMap<>();
155                params.put("grant_type", GRANT_TYPE.getValue());
156                params.put("code", code.getValue());
157
158                if (redirectURI != null)
159                        params.put("redirect_uri", redirectURI.toString());
160
161                if (codeVerifier != null)
162                        params.put("code_verifier", codeVerifier.getValue());
163
164                return params;
165        }
166
167
168        @Override
169        public boolean equals(Object o) {
170                if (this == o) return true;
171                if (!(o instanceof AuthorizationCodeGrant)) return false;
172
173                AuthorizationCodeGrant codeGrant = (AuthorizationCodeGrant) o;
174
175                if (!code.equals(codeGrant.code)) return false;
176                if (redirectURI != null ? !redirectURI.equals(codeGrant.redirectURI) : codeGrant.redirectURI != null)
177                        return false;
178                return codeVerifier != null ? codeVerifier.equals(codeGrant.codeVerifier) : codeGrant.codeVerifier == null;
179
180        }
181
182
183        @Override
184        public int hashCode() {
185                int result = code.hashCode();
186                result = 31 * result + (redirectURI != null ? redirectURI.hashCode() : 0);
187                result = 31 * result + (codeVerifier != null ? codeVerifier.hashCode() : 0);
188                return result;
189        }
190
191
192        /**
193         * Parses an authorisation code grant from the specified parameters.
194         *
195         * <p>Example:
196         *
197         * <pre>
198         * grant_type=authorization_code
199         * code=SplxlOBeZQQYbYS6WxSbIA
200         * redirect_uri=https://Fclient.example.com/cb
201         * </pre>
202         *
203         * @param params The parameters.
204         *
205         * @return The authorisation code grant.
206         *
207         * @throws ParseException If parsing failed.
208         */
209        public static AuthorizationCodeGrant parse(final Map<String,String> params)
210                throws ParseException {
211
212                // Parse grant type
213                String grantTypeString = params.get("grant_type");
214
215                if (grantTypeString == null) {
216                        String msg = "Missing \"grant_type\" parameter";
217                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
218                }
219
220                if (! GrantType.parse(grantTypeString).equals(GRANT_TYPE)) {
221                        String msg = "The \"grant_type\" must be \"" + GRANT_TYPE + "\"";
222                        throw new ParseException(msg, OAuth2Error.UNSUPPORTED_GRANT_TYPE.appendDescription(": " + msg));
223                }
224
225                // Parse authorisation code
226                String codeString = params.get("code");
227
228                if (codeString == null || codeString.trim().isEmpty()) {
229                        String msg = "Missing or empty \"code\" parameter";
230                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
231                }
232
233                AuthorizationCode code = new AuthorizationCode(codeString);
234
235                // Parse optional redirection URI
236                String redirectURIString = params.get("redirect_uri");
237
238                URI redirectURI = null;
239
240                if (redirectURIString != null) {
241                        try {
242                                redirectURI = new URI(redirectURIString);
243                        } catch (URISyntaxException e) {
244                                String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage();
245                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), e);
246                        }
247                }
248
249
250                // Parse optional code verifier
251                String codeVerifierString = params.get("code_verifier");
252
253                CodeVerifier codeVerifier = null;
254
255                if (StringUtils.isNotBlank(codeVerifierString)) {
256
257                        try {
258                                codeVerifier = new CodeVerifier(codeVerifierString);
259                        } catch (IllegalArgumentException e) {
260                                // Illegal code verifier
261                                throw new ParseException(e.getMessage(), e);
262                        }
263                }
264
265                return new AuthorizationCodeGrant(code, redirectURI, codeVerifier);
266        }
267}