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