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.lang3.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}