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. This class is immutable. 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 * @author Vladimir Dzhuvinov 053 */ 054@Immutable 055public class AuthorizationErrorResponse 056 extends AuthorizationResponse 057 implements ErrorResponse { 058 059 060 /** 061 * The standard OAuth 2.0 errors for an Authorisation error response. 062 */ 063 private static Set<ErrorObject> stdErrors = new HashSet<ErrorObject>(); 064 065 066 static { 067 stdErrors.add(OAuth2Error.INVALID_REQUEST); 068 stdErrors.add(OAuth2Error.UNAUTHORIZED_CLIENT); 069 stdErrors.add(OAuth2Error.ACCESS_DENIED); 070 stdErrors.add(OAuth2Error.UNSUPPORTED_RESPONSE_TYPE); 071 stdErrors.add(OAuth2Error.INVALID_SCOPE); 072 stdErrors.add(OAuth2Error.SERVER_ERROR); 073 stdErrors.add(OAuth2Error.TEMPORARILY_UNAVAILABLE); 074 } 075 076 077 /** 078 * Gets the standard OAuth 2.0 errors for an Authorisation error 079 * response. 080 * 081 * @return The standard errors, as a read-only set. 082 */ 083 public static Set<ErrorObject> getStandardErrors() { 084 085 return Collections.unmodifiableSet(stdErrors); 086 } 087 088 089 /** 090 * The error. 091 */ 092 private final ErrorObject error; 093 094 095 /** 096 * The response type, used to determine redirect URL composition. If 097 * unknown {@code null}. 098 */ 099 private final ResponseType rt; 100 101 102 /** 103 * Creates a new authorisation error response. 104 * 105 * @param redirectURI The base redirect URI. Must not be {@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 redirect 111 * URI composition. If unknown {@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 @Override 131 public ErrorObject getErrorObject() { 132 133 return error; 134 } 135 136 137 /** 138 * Gets the response type. 139 * 140 * @return The response type, {@code null} if not specified. 141 */ 142 public ResponseType getResponseType() { 143 144 return rt; 145 } 146 147 148 @Override 149 public Map<String,String> toParameters() { 150 151 Map<String,String> params = new HashMap<String,String>(); 152 153 params.put("error", error.getCode()); 154 155 if (error.getDescription() != null) 156 params.put("error_description", error.getDescription()); 157 158 if (error.getURI() != null) 159 params.put("error_uri", error.getURI().toString()); 160 161 if (getState() != null) 162 params.put("state", getState().getValue()); 163 164 return params; 165 } 166 167 168 @Override 169 public URL toURI() 170 throws SerializeException { 171 172 StringBuilder sb = new StringBuilder(getRedirectionURI().toString()); 173 174 if (rt == null || rt.contains(ResponseType.Value.TOKEN)) 175 sb.append("#"); 176 else 177 sb.append("?"); 178 179 sb.append(URLUtils.serializeParameters(toParameters())); 180 181 try { 182 return new URL(sb.toString()); 183 184 } catch (MalformedURLException e) { 185 186 throw new SerializeException("Couldn't serialize redirect URL: " + e.getMessage(), e); 187 } 188 } 189 190 191 /** 192 * Parses an authorisation error response from the specified redirect 193 * URI and parameters. 194 * 195 * @param redirectURI The base redirect URI. Must not be {@code null}. 196 * @param params The response parameters to parse. Must not be 197 * {@code null}. 198 * 199 * @return The authorisation error response. 200 * 201 * @throws ParseException If the parameters couldn't be parsed to an 202 * authorisation error response. 203 */ 204 public static AuthorizationErrorResponse parse(final URL redirectURI, 205 final Map<String,String> params) 206 throws ParseException { 207 208 // Parse the error 209 if (StringUtils.isBlank(params.get("error"))) 210 throw new ParseException("Missing error code"); 211 212 // Parse error code 213 String errorCode = params.get("error"); 214 215 String errorDescription = params.get("error_description"); 216 217 String errorURIString = params.get("error_uri"); 218 219 URL errorURI = null; 220 221 if (errorURIString != null) { 222 223 try { 224 errorURI = new URL(errorURIString); 225 226 } catch (MalformedURLException e) { 227 228 throw new ParseException("Invalid error URI: " + errorURIString, e); 229 } 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, null, state); 240 } 241 242 243 /** 244 * Parses an authorisation error response from the specified URI. 245 * 246 * <p>Example URI: 247 * 248 * <pre> 249 * https://client.example.com/cb? 250 * error=invalid_request 251 * &error_description=the%20request%20is%20not%20valid%20or%20malformed 252 * &state=af0ifjsldkj 253 * </pre> 254 * 255 * @param uri The URI to parse. Can be absolute or relative. Must not 256 * be {@code null}. 257 * 258 * @return The authorisation error response. 259 * 260 * @throws ParseException If the URI couldn't be parsed to an 261 * authorisation error response. 262 */ 263 public static AuthorizationErrorResponse parse(final URL uri) 264 throws ParseException { 265 266 Map<String,String> params = null; 267 268 if (uri.getRef() != null) 269 params = URLUtils.parseParameters(uri.getRef()); 270 271 else if (uri.getQuery() != null) 272 params = URLUtils.parseParameters(uri.getQuery()); 273 274 else 275 throw new ParseException("Missing URL fragment or query string"); 276 277 278 return parse(URLUtils.getBaseURL(uri), params); 279 } 280 281 282 /** 283 * Parses an authorisation error response from the specified HTTP 284 * response. 285 * 286 * <p>Example HTTP response: 287 * 288 * <pre> 289 * HTTP/1.1 302 Found 290 * Location: https://client.example.com/cb? 291 * error=invalid_request 292 * &error_description=the%20request%20is%20not%20valid%20or%20malformed 293 * &state=af0ifjsldkj 294 * </pre> 295 * 296 * @param httpResponse The HTTP response to parse. Must not be 297 * {@code null}. 298 * 299 * @return The authorisation error response. 300 * 301 * @throws ParseException If the HTTP response couldn't be parsed to an 302 * authorisation error response. 303 */ 304 public static AuthorizationErrorResponse parse(final HTTPResponse httpResponse) 305 throws ParseException { 306 307 if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND) 308 throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 309 httpResponse.getStatusCode()); 310 311 URL location = httpResponse.getLocation(); 312 313 if (location == null) 314 throw new ParseException("Missing redirect URL / HTTP Location header"); 315 316 return parse(location); 317 } 318}