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.util.*; 023 024import net.jcip.annotations.Immutable; 025 026import com.nimbusds.jwt.JWT; 027import com.nimbusds.jwt.JWTParser; 028import com.nimbusds.oauth2.sdk.http.HTTPRequest; 029import com.nimbusds.oauth2.sdk.http.HTTPResponse; 030import com.nimbusds.oauth2.sdk.id.Issuer; 031import com.nimbusds.oauth2.sdk.id.State; 032import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 033import com.nimbusds.oauth2.sdk.util.StringUtils; 034import com.nimbusds.oauth2.sdk.util.URIUtils; 035 036 037/** 038 * Authorisation error response. Intended only for errors which are allowed to 039 * be communicated back to the requesting OAuth 2.0 client, such as 040 * {@code access_denied}. For a complete list see OAuth 2.0 (RFC 6749), 041 * sections 4.1.2.1 and 4.2.2.1. 042 * 043 * <p>If the authorisation request fails due to a missing, invalid, or 044 * mismatching {@code redirect_uri}, or if the {@code client_id} is missing or 045 * invalid, a response <strong>must not</strong> be sent back to the requesting 046 * client. Instead, the authorisation server should simply display the error 047 * to the resource owner. 048 * 049 * <p>Standard authorisation errors: 050 * 051 * <ul> 052 * <li>{@link OAuth2Error#INVALID_REQUEST} 053 * <li>{@link OAuth2Error#UNAUTHORIZED_CLIENT} 054 * <li>{@link OAuth2Error#ACCESS_DENIED} 055 * <li>{@link OAuth2Error#UNSUPPORTED_RESPONSE_TYPE} 056 * <li>{@link OAuth2Error#INVALID_SCOPE} 057 * <li>{@link OAuth2Error#SERVER_ERROR} 058 * <li>{@link OAuth2Error#TEMPORARILY_UNAVAILABLE} 059 * </ul> 060 * 061 * <p>Example HTTP response: 062 * 063 * <pre> 064 * HTTP/1.1 302 Found 065 * Location: https://client.example.com/cb? 066 * error=invalid_request 067 * &error_description=the%20request%20is%20not%20valid%20or%20malformed 068 * &state=af0ifjsldkj 069 * </pre> 070 * 071 * <p>Related specifications: 072 * 073 * <ul> 074 * <li>OAuth 2.0 (RFC 6749), sections 4.1.2.1 and 4.2.2.1. 075 * <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0. 076 * <li>OAuth 2.0 Form Post Response Mode 1.0. 077 * <li>Financial-grade API: JWT Secured Authorization Response Mode for 078 * OAuth 2.0 (JARM). 079 * <li>OAuth 2.0 Authorization Server Issuer Identification (RFC 9207) 080 * </ul> 081 */ 082@Immutable 083public class AuthorizationErrorResponse 084 extends AuthorizationResponse 085 implements ErrorResponse { 086 087 088 /** 089 * The standard OAuth 2.0 errors for an Authorisation error response. 090 */ 091 private static final Set<ErrorObject> stdErrors = new HashSet<>(); 092 093 094 static { 095 stdErrors.add(OAuth2Error.INVALID_REQUEST); 096 stdErrors.add(OAuth2Error.UNAUTHORIZED_CLIENT); 097 stdErrors.add(OAuth2Error.ACCESS_DENIED); 098 stdErrors.add(OAuth2Error.UNSUPPORTED_RESPONSE_TYPE); 099 stdErrors.add(OAuth2Error.INVALID_SCOPE); 100 stdErrors.add(OAuth2Error.SERVER_ERROR); 101 stdErrors.add(OAuth2Error.TEMPORARILY_UNAVAILABLE); 102 } 103 104 105 /** 106 * Gets the standard OAuth 2.0 errors for an Authorisation error 107 * response. 108 * 109 * @return The standard errors, as a read-only set. 110 */ 111 public static Set<ErrorObject> getStandardErrors() { 112 113 return Collections.unmodifiableSet(stdErrors); 114 } 115 116 117 /** 118 * The error. 119 */ 120 private final ErrorObject error; 121 122 123 /** 124 * Creates a new authorisation error response. 125 * 126 * @param redirectURI The base redirection URI. Must not be 127 * {@code null}. 128 * @param error The error. Should match one of the 129 * {@link #getStandardErrors standard errors} for an 130 * authorisation error response. Must not be 131 * {@code null}. 132 * @param state The state, {@code null} if not requested. 133 * @param rm The implied response mode, {@code null} if 134 * unknown. 135 */ 136 public AuthorizationErrorResponse(final URI redirectURI, 137 final ErrorObject error, 138 final State state, 139 final ResponseMode rm) { 140 141 this(redirectURI, error, state, null, rm); 142 } 143 144 145 /** 146 * Creates a new authorisation error response. 147 * 148 * @param redirectURI The base redirection URI. Must not be 149 * {@code null}. 150 * @param error The error. Should match one of the 151 * {@link #getStandardErrors standard errors} for an 152 * authorisation error response. Must not be 153 * {@code null}. 154 * @param state The state, {@code null} if not requested. 155 * @param issuer The issuer, {@code null} if not specified. 156 * @param rm The implied response mode, {@code null} if 157 * unknown. 158 */ 159 public AuthorizationErrorResponse(final URI redirectURI, 160 final ErrorObject error, 161 final State state, 162 final Issuer issuer, 163 final ResponseMode rm) { 164 165 super(redirectURI, state, issuer, rm); 166 167 if (error == null) 168 throw new IllegalArgumentException("The error must not be null"); 169 170 this.error = error; 171 } 172 173 174 /** 175 * Creates a new JSON Web Token (JWT) secured authorisation error 176 * response. 177 * 178 * @param redirectURI The base redirection URI. Must not be 179 * {@code null}. 180 * @param jwtResponse The JWT-secured response. Must not be 181 * {@code null}. 182 * @param rm The implied response mode, {@code null} if 183 * unknown. 184 */ 185 public AuthorizationErrorResponse(final URI redirectURI, 186 final JWT jwtResponse, 187 final ResponseMode rm) { 188 189 super(redirectURI, jwtResponse, rm); 190 191 error = null; 192 } 193 194 195 @Override 196 public boolean indicatesSuccess() { 197 198 return false; 199 } 200 201 202 @Override 203 public ErrorObject getErrorObject() { 204 205 return error; 206 } 207 208 209 @Override 210 public ResponseMode impliedResponseMode() { 211 212 // Return "query" if not known, assumed the most frequent case 213 return getResponseMode() != null ? getResponseMode() : ResponseMode.QUERY; 214 } 215 216 217 @Override 218 public Map<String,List<String>> toParameters() { 219 220 Map<String,List<String>> params = new HashMap<>(); 221 222 if (getJWTResponse() != null) { 223 // JARM, no other top-level parameters 224 params.put("response", Collections.singletonList(getJWTResponse().serialize())); 225 return params; 226 } 227 228 params.putAll(getErrorObject().toParameters()); 229 230 if (getState() != null) 231 params.put("state", Collections.singletonList(getState().getValue())); 232 233 if (getIssuer() != null) 234 params.put("iss", Collections.singletonList(getIssuer().getValue())); 235 236 return params; 237 } 238 239 240 /** 241 * Parses an authorisation error response. 242 * 243 * @param redirectURI The base redirection URI. Must not be 244 * {@code null}. 245 * @param params The response parameters to parse. Must not be 246 * {@code null}. 247 * 248 * @return The authorisation error response. 249 * 250 * @throws ParseException If the parameters couldn't be parsed to an 251 * authorisation error response. 252 */ 253 public static AuthorizationErrorResponse parse(final URI redirectURI, 254 final Map<String,List<String>> params) 255 throws ParseException { 256 257 // JARM, ignore other top level params 258 String responseString = MultivaluedMapUtils.getFirstValue(params, "response"); 259 if (responseString != null) { 260 JWT jwtResponse; 261 try { 262 jwtResponse = JWTParser.parse(responseString); 263 } catch (java.text.ParseException e) { 264 throw new ParseException("Invalid JWT response: " + e.getMessage(), e); 265 } 266 267 return new AuthorizationErrorResponse(redirectURI, jwtResponse, ResponseMode.JWT); 268 } 269 270 // Parse the error 271 ErrorObject error = ErrorObject.parse(params); 272 273 if (StringUtils.isBlank(error.getCode())) { 274 throw new ParseException("Missing error code"); 275 } 276 error = error.setHTTPStatusCode(HTTPResponse.SC_FOUND); // need a status code 277 278 // State 279 State state = State.parse(MultivaluedMapUtils.getFirstValue(params, "state")); 280 281 // Parse optional issuer, OAuth 2.0 Authorization Server Issuer Identification (RFC 9207) 282 Issuer issuer = Issuer.parse(MultivaluedMapUtils.getFirstValue(params, "iss")); 283 284 return new AuthorizationErrorResponse(redirectURI, error, state, issuer, null); 285 } 286 287 288 /** 289 * Parses an authorisation error response. 290 * 291 * <p>Use a relative URI if the host, port and path details are not 292 * known: 293 * 294 * <pre> 295 * URI relUrl = new URI("https:///?error=invalid_request"); 296 * </pre> 297 * 298 * <p>Example URI: 299 * 300 * <pre> 301 * https://client.example.com/cb? 302 * error=invalid_request 303 * &error_description=the%20request%20is%20not%20valid%20or%20malformed 304 * &state=af0ifjsldkj 305 * </pre> 306 * 307 * @param uri The URI to parse. Can be absolute or relative, with a 308 * fragment or query string containing the authorisation 309 * response parameters. Must not be {@code null}. 310 * 311 * @return The authorisation error response. 312 * 313 * @throws ParseException If the URI couldn't be parsed to an 314 * authorisation error response. 315 */ 316 public static AuthorizationErrorResponse parse(final URI uri) 317 throws ParseException { 318 319 return parse(URIUtils.getBaseURI(uri), parseResponseParameters(uri)); 320 } 321 322 323 /** 324 * Parses an authorisation error response from the specified initial 325 * HTTP 302 redirect response generated at the authorisation endpoint. 326 * 327 * <p>Example HTTP response: 328 * 329 * <pre> 330 * HTTP/1.1 302 Found 331 * Location: https://client.example.com/cb?error=invalid_request&state=af0ifjsldkj 332 * </pre> 333 * 334 * @see #parse(HTTPRequest) 335 * 336 * @param httpResponse The HTTP response to parse. Must not be 337 * {@code null}. 338 * 339 * @return The authorisation error response. 340 * 341 * @throws ParseException If the HTTP response couldn't be parsed to an 342 * authorisation error response. 343 */ 344 public static AuthorizationErrorResponse parse(final HTTPResponse httpResponse) 345 throws ParseException { 346 347 URI location = httpResponse.getLocation(); 348 349 if (location == null) { 350 throw new ParseException("Missing redirection URL / HTTP Location header"); 351 } 352 353 return parse(location); 354 } 355 356 357 /** 358 * Parses an authorisation error response from the specified HTTP 359 * request at the client redirection (callback) URI. Applies to 360 * {@code query}, {@code fragment} and {@code form_post} response 361 * modes. 362 * 363 * <p>Example HTTP request (authorisation success): 364 * 365 * <pre> 366 * GET /cb?error=invalid_request&state=af0ifjsldkj HTTP/1.1 367 * Host: client.example.com 368 * </pre> 369 * 370 * @see #parse(HTTPResponse) 371 * 372 * @param httpRequest The HTTP request to parse. Must not be 373 * {@code null}. 374 * 375 * @return The authorisation error response. 376 * 377 * @throws ParseException If the HTTP request couldn't be parsed to an 378 * authorisation error response. 379 */ 380 public static AuthorizationErrorResponse parse(final HTTPRequest httpRequest) 381 throws ParseException { 382 383 return parse(httpRequest.getURI(), parseResponseParameters(httpRequest)); 384 } 385}