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