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