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