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}