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 com.nimbusds.oauth2.sdk.pkce.CodeVerifier; 022import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 023import com.nimbusds.oauth2.sdk.util.StringUtils; 024import net.jcip.annotations.Immutable; 025 026import java.net.URI; 027import java.net.URISyntaxException; 028import java.util.*; 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) 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 this.code = Objects.requireNonNull(code); 104 this.redirectURI = redirectURI; 105 this.codeVerifier = codeVerifier; 106 } 107 108 109 /** 110 * Gets the authorisation code. 111 * 112 * @return The authorisation code. 113 */ 114 public AuthorizationCode getAuthorizationCode() { 115 116 return code; 117 } 118 119 120 /** 121 * Gets the redirection URI of the original authorisation request. 122 * 123 * @return The redirection URI, {@code null} if the 124 * {@code redirect_uri} parameter was not included in the 125 * original authorisation request. 126 */ 127 public URI getRedirectionURI() { 128 129 return redirectURI; 130 } 131 132 133 /** 134 * Gets the authorisation code verifier for PKCE. 135 * 136 * @return The authorisation code verifier, {@code null} if not 137 * specified. 138 */ 139 public CodeVerifier getCodeVerifier() { 140 141 return codeVerifier; 142 } 143 144 145 @Override 146 public Map<String,List<String>> toParameters() { 147 148 Map<String,List<String>> params = new LinkedHashMap<>(); 149 params.put("grant_type", Collections.singletonList(GRANT_TYPE.getValue())); 150 params.put("code", Collections.singletonList(code.getValue())); 151 152 if (redirectURI != null) 153 params.put("redirect_uri", Collections.singletonList(redirectURI.toString())); 154 155 if (codeVerifier != null) 156 params.put("code_verifier", Collections.singletonList(codeVerifier.getValue())); 157 158 return params; 159 } 160 161 162 @Override 163 public boolean equals(Object o) { 164 if (this == o) return true; 165 if (!(o instanceof AuthorizationCodeGrant)) return false; 166 AuthorizationCodeGrant that = (AuthorizationCodeGrant) o; 167 return code.equals(that.code) && Objects.equals(redirectURI, that.redirectURI) && Objects.equals(getCodeVerifier(), that.getCodeVerifier()); 168 } 169 170 171 @Override 172 public int hashCode() { 173 return Objects.hash(code, redirectURI, getCodeVerifier()); 174 } 175 176 177 /** 178 * Parses an authorisation code grant from the specified request body 179 * parameters. 180 * 181 * <p>Example: 182 * 183 * <pre> 184 * grant_type=authorization_code 185 * code=SplxlOBeZQQYbYS6WxSbIA 186 * redirect_uri=https://Fclient.example.com/cb 187 * </pre> 188 * 189 * @param params The parameters. 190 * 191 * @return The authorisation code grant. 192 * 193 * @throws ParseException If parsing failed. 194 */ 195 public static AuthorizationCodeGrant parse(final Map<String,List<String>> params) 196 throws ParseException { 197 198 GrantType.ensure(GRANT_TYPE, params); 199 200 // Parse authorisation code 201 String codeString = MultivaluedMapUtils.getFirstValue(params, "code"); 202 203 if (codeString == null || codeString.trim().isEmpty()) { 204 String msg = "Missing or empty code parameter"; 205 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 206 } 207 208 AuthorizationCode code = new AuthorizationCode(codeString); 209 210 // Parse optional redirection URI 211 String redirectURIString = MultivaluedMapUtils.getFirstValue(params, "redirect_uri"); 212 213 URI redirectURI = null; 214 215 if (redirectURIString != null) { 216 try { 217 redirectURI = new URI(redirectURIString); 218 } catch (URISyntaxException e) { 219 String msg = "Invalid redirect_uri parameter: " + e.getMessage(); 220 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), e); 221 } 222 } 223 224 225 // Parse optional code verifier 226 String codeVerifierString = MultivaluedMapUtils.getFirstValue(params,"code_verifier"); 227 228 CodeVerifier codeVerifier = null; 229 230 if (StringUtils.isNotBlank(codeVerifierString)) { 231 232 try { 233 codeVerifier = new CodeVerifier(codeVerifierString); 234 } catch (IllegalArgumentException e) { 235 // Illegal code verifier 236 String msg = "Illegal code verifier: " + e.getMessage(); 237 throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), e); 238 } 239 } 240 241 return new AuthorizationCodeGrant(code, redirectURI, codeVerifier); 242 } 243}