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 String msg = "Illegal code verifier: " + e.getMessage(); 244 throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), e); 245 } 246 } 247 248 return new AuthorizationCodeGrant(code, redirectURI, codeVerifier); 249 } 250}