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