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