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