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