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 com.nimbusds.oauth2.sdk.util.StringUtils; 028import net.jcip.annotations.Immutable; 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}