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