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.HashMap; 025import java.util.HashSet; 026import java.util.Map; 027import java.util.Set; 028 029import net.jcip.annotations.Immutable; 030 031import org.apache.commons.lang3.StringUtils; 032 033import com.nimbusds.oauth2.sdk.id.State; 034import com.nimbusds.oauth2.sdk.http.HTTPRequest; 035import com.nimbusds.oauth2.sdk.http.HTTPResponse; 036import com.nimbusds.oauth2.sdk.util.URIUtils; 037import com.nimbusds.oauth2.sdk.util.URLUtils; 038 039 040/** 041 * Authorisation error response. Intended only for errors which are allowed to 042 * be communicated back to the requesting OAuth 2.0 client, such as 043 * {@code access_denied}. For a complete list see OAuth 2.0 (RFC 6749), 044 * sections 4.1.2.1 and 4.2.2.1. 045 * 046 * <p>If the authorisation request fails due to a missing, invalid, or 047 * mismatching {@code redirect_uri}, or if the {@code client_id} is missing or 048 * invalid, a response <strong>must not</strong> be sent back to the requesting 049 * client. Instead, the authorisation server should simply display the error 050 * to the resource owner. 051 * 052 * <p>Standard authorisation errors: 053 * 054 * <ul> 055 * <li>{@link OAuth2Error#INVALID_REQUEST} 056 * <li>{@link OAuth2Error#UNAUTHORIZED_CLIENT} 057 * <li>{@link OAuth2Error#ACCESS_DENIED} 058 * <li>{@link OAuth2Error#UNSUPPORTED_RESPONSE_TYPE} 059 * <li>{@link OAuth2Error#INVALID_SCOPE} 060 * <li>{@link OAuth2Error#SERVER_ERROR} 061 * <li>{@link OAuth2Error#TEMPORARILY_UNAVAILABLE} 062 * </ul> 063 * 064 * <p>Example HTTP response: 065 * 066 * <pre> 067 * HTTP/1.1 302 Found 068 * Location: https://client.example.com/cb? 069 * error=invalid_request 070 * &error_description=the%20request%20is%20not%20valid%20or%20malformed 071 * &state=af0ifjsldkj 072 * </pre> 073 * 074 * <p>Related specifications: 075 * 076 * <ul> 077 * <li>OAuth 2.0 (RFC 6749), sections 4.1.2.1 and 4.2.2.1. 078 * <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0. 079 * <li>OAuth 2.0 Form Post Response Mode 1.0. 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 super(redirectURI, state, rm); 142 143 if (error == null) 144 throw new IllegalArgumentException("The error must not be null"); 145 146 this.error = error; 147 } 148 149 150 @Override 151 public boolean indicatesSuccess() { 152 153 return false; 154 } 155 156 157 @Override 158 public ErrorObject getErrorObject() { 159 160 return error; 161 } 162 163 164 @Override 165 public ResponseMode impliedResponseMode() { 166 167 // Return "query" if not known, assumed the most frequent case 168 return getResponseMode() != null ? getResponseMode() : ResponseMode.QUERY; 169 } 170 171 172 @Override 173 public Map<String,String> toParameters() { 174 175 Map<String,String> params = new HashMap<>(); 176 177 params.put("error", error.getCode()); 178 179 if (error.getDescription() != null) 180 params.put("error_description", error.getDescription()); 181 182 if (error.getURI() != null) 183 params.put("error_uri", error.getURI().toString()); 184 185 if (getState() != null) 186 params.put("state", getState().getValue()); 187 188 return params; 189 } 190 191 192 /** 193 * Parses an authorisation error response. 194 * 195 * @param redirectURI The base redirection URI. Must not be 196 * {@code null}. 197 * @param params The response parameters to parse. Must not be 198 * {@code null}. 199 * 200 * @return The authorisation error response. 201 * 202 * @throws ParseException If the parameters couldn't be parsed to an 203 * authorisation error response. 204 */ 205 public static AuthorizationErrorResponse parse(final URI redirectURI, 206 final Map<String,String> params) 207 throws ParseException { 208 209 // Parse the error 210 if (StringUtils.isBlank(params.get("error"))) 211 throw new ParseException("Missing error code"); 212 213 // Parse error code 214 String errorCode = params.get("error"); 215 216 String errorDescription = params.get("error_description"); 217 218 String errorURIString = params.get("error_uri"); 219 220 URI errorURI = null; 221 222 if (errorURIString != null) { 223 224 try { 225 errorURI = new URI(errorURIString); 226 227 } catch (URISyntaxException e) { 228 229 throw new ParseException("Invalid error URI: " + errorURIString, e); 230 } 231 } 232 233 ErrorObject error = new ErrorObject(errorCode, errorDescription, HTTPResponse.SC_FOUND, errorURI); 234 235 236 // State 237 State state = State.parse(params.get("state")); 238 239 return new AuthorizationErrorResponse(redirectURI, error, state, null); 240 } 241 242 243 /** 244 * Parses an authorisation error response. 245 * 246 * <p>Use a relative URI if the host, port and path details are not 247 * known: 248 * 249 * <pre> 250 * URI relUrl = new URI("https:///?error=invalid_request"); 251 * </pre> 252 * 253 * <p>Example URI: 254 * 255 * <pre> 256 * https://client.example.com/cb? 257 * error=invalid_request 258 * &error_description=the%20request%20is%20not%20valid%20or%20malformed 259 * &state=af0ifjsldkj 260 * </pre> 261 * 262 * @param uri The URI to parse. Can be absolute or relative, with a 263 * fragment or query string containing the authorisation 264 * response parameters. Must not be {@code null}. 265 * 266 * @return The authorisation error response. 267 * 268 * @throws ParseException If the URI couldn't be parsed to an 269 * authorisation error response. 270 */ 271 public static AuthorizationErrorResponse parse(final URI uri) 272 throws ParseException { 273 274 Map<String,String> params; 275 276 if (uri.getRawFragment() != null) { 277 278 params = URLUtils.parseParameters(uri.getRawFragment()); 279 280 } else if (uri.getRawQuery() != null) { 281 282 params = URLUtils.parseParameters(uri.getRawQuery()); 283 284 } else { 285 286 throw new ParseException("Missing URI fragment or query string"); 287 } 288 289 290 return parse(URIUtils.getBaseURI(uri), params); 291 } 292 293 294 /** 295 * Parses an authorisation error response from the specified initial 296 * HTTP 302 redirect response generated at the authorisation endpoint. 297 * 298 * <p>Example HTTP response: 299 * 300 * <pre> 301 * HTTP/1.1 302 Found 302 * Location: https://client.example.com/cb?error=invalid_request&state=af0ifjsldkj 303 * </pre> 304 * 305 * @see #parse(HTTPRequest) 306 * 307 * @param httpResponse The HTTP response to parse. Must not be 308 * {@code null}. 309 * 310 * @return The authorisation error response. 311 * 312 * @throws ParseException If the HTTP response couldn't be parsed to an 313 * authorisation error response. 314 */ 315 public static AuthorizationErrorResponse parse(final HTTPResponse httpResponse) 316 throws ParseException { 317 318 URI location = httpResponse.getLocation(); 319 320 if (location == null) { 321 throw new ParseException("Missing redirection URL / HTTP Location header"); 322 } 323 324 return parse(location); 325 } 326 327 328 /** 329 * Parses an authorisation error response from the specified HTTP 330 * request at the client redirection (callback) URI. Applies to 331 * {@code query}, {@code fragment} and {@code form_post} response 332 * modes. 333 * 334 * <p>Example HTTP request (authorisation success): 335 * 336 * <pre> 337 * GET /cb?error=invalid_request&state=af0ifjsldkj HTTP/1.1 338 * Host: client.example.com 339 * </pre> 340 * 341 * @see #parse(HTTPResponse) 342 * 343 * @param httpRequest The HTTP request to parse. Must not be 344 * {@code null}. 345 * 346 * @throws ParseException If the HTTP request couldn't be parsed to an 347 * authorisation error response. 348 */ 349 public static AuthorizationErrorResponse parse(final HTTPRequest httpRequest) 350 throws ParseException { 351 352 final URI baseURI; 353 354 try { 355 baseURI = httpRequest.getURL().toURI(); 356 357 } catch (URISyntaxException e) { 358 throw new ParseException(e.getMessage(), e); 359 } 360 361 if (httpRequest.getQuery() != null) { 362 // For query string and form_post response mode 363 return parse(baseURI, URLUtils.parseParameters(httpRequest.getQuery())); 364 } else if (httpRequest.getFragment() != null) { 365 // For fragment response mode (never available in actual HTTP request from browser) 366 return parse(baseURI, URLUtils.parseParameters(httpRequest.getFragment())); 367 } else { 368 throw new ParseException("Missing URI fragment, query string or post body"); 369 } 370 } 371}