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.openid.connect.sdk; 019 020 021import java.net.URI; 022import java.net.URISyntaxException; 023import java.util.Collections; 024import java.util.HashSet; 025import java.util.Map; 026import java.util.Set; 027 028import com.nimbusds.oauth2.sdk.AuthorizationErrorResponse; 029import com.nimbusds.oauth2.sdk.ErrorObject; 030import com.nimbusds.oauth2.sdk.ParseException; 031import com.nimbusds.oauth2.sdk.ResponseMode; 032import com.nimbusds.oauth2.sdk.http.HTTPRequest; 033import com.nimbusds.oauth2.sdk.http.HTTPResponse; 034import com.nimbusds.oauth2.sdk.id.State; 035import com.nimbusds.oauth2.sdk.util.URLUtils; 036import net.jcip.annotations.Immutable; 037 038 039/** 040 * OpenID Connect authentication error response. Intended only for errors which 041 * are allowed to be communicated back to the requesting OAuth 2.0 client, such 042 * as {@code access_denied}. For a complete list see OAuth 2.0 (RFC 6749), 043 * sections 4.1.2.1 and 4.2.2.1, OpenID Connect Core 1.0 section 3.1.2.6. 044 * 045 * <p>If the authorisation request fails due to a missing, invalid, or 046 * mismatching {@code redirect_uri}, or if the {@code client_id} is missing or 047 * invalid, a response <strong>must not</strong> be sent back to the requesting 048 * client. Instead, the OpenID provider should simply display the error to the 049 * end-user. 050 * 051 * <p>Standard errors: 052 * 053 * <ul> 054 * <li>OAuth 2.0 authorisation errors: 055 * <ul> 056 * <li>{@link com.nimbusds.oauth2.sdk.OAuth2Error#INVALID_REQUEST} 057 * <li>{@link com.nimbusds.oauth2.sdk.OAuth2Error#UNAUTHORIZED_CLIENT} 058 * <li>{@link com.nimbusds.oauth2.sdk.OAuth2Error#ACCESS_DENIED} 059 * <li>{@link com.nimbusds.oauth2.sdk.OAuth2Error#UNSUPPORTED_RESPONSE_TYPE} 060 * <li>{@link com.nimbusds.oauth2.sdk.OAuth2Error#INVALID_SCOPE} 061 * <li>{@link com.nimbusds.oauth2.sdk.OAuth2Error#SERVER_ERROR} 062 * <li>{@link com.nimbusds.oauth2.sdk.OAuth2Error#TEMPORARILY_UNAVAILABLE} 063 * </ul> 064 * <li>OpenID Connect specific errors: 065 * <ul> 066 * <li>{@link OIDCError#INTERACTION_REQUIRED} 067 * <li>{@link OIDCError#LOGIN_REQUIRED} 068 * <li>{@link OIDCError#ACCOUNT_SELECTION_REQUIRED} 069 * <li>{@link OIDCError#CONSENT_REQUIRED} 070 * <li>{@link OIDCError#INVALID_REQUEST_URI} 071 * <li>{@link OIDCError#INVALID_REQUEST_OBJECT} 072 * <li>{@link OIDCError#REGISTRATION_NOT_SUPPORTED} 073 * <li>{@link OIDCError#REQUEST_NOT_SUPPORTED} 074 * <li>{@link OIDCError#REQUEST_URI_NOT_SUPPORTED} 075 * </ul> 076 * </li> 077 * </ul> 078 * 079 * <p>Example HTTP response: 080 * 081 * <pre> 082 * HTTP/1.1 302 Found 083 * Location: https://client.example.org/cb? 084 * error=invalid_request 085 * &error_description=the%20request%20is%20not%20valid%20or%20malformed 086 * &state=af0ifjsldkj 087 * </pre> 088 * 089 * <p>Related specifications: 090 * 091 * <ul> 092 * <li>OpenID Connect Core 1.0, section 3.1.2.6. 093 * <li>OAuth 2.0 (RFC 6749), sections 4.1.2.1 and 4.2.2.1. 094 * <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0. 095 * <li>OAuth 2.0 Form Post Response Mode 1.0. 096 * </ul> 097 */ 098@Immutable 099public class AuthenticationErrorResponse 100 extends AuthorizationErrorResponse 101 implements AuthenticationResponse { 102 103 104 /** 105 * The standard errors for an OpenID Connect authentication error 106 * response. 107 */ 108 private static final Set<ErrorObject> stdErrors = new HashSet<>(); 109 110 111 static { 112 stdErrors.addAll(AuthorizationErrorResponse.getStandardErrors()); 113 114 stdErrors.add(OIDCError.INTERACTION_REQUIRED); 115 stdErrors.add(OIDCError.LOGIN_REQUIRED); 116 stdErrors.add(OIDCError.ACCOUNT_SELECTION_REQUIRED); 117 stdErrors.add(OIDCError.CONSENT_REQUIRED); 118 stdErrors.add(OIDCError.INVALID_REQUEST_URI); 119 stdErrors.add(OIDCError.INVALID_REQUEST_OBJECT); 120 stdErrors.add(OIDCError.REGISTRATION_NOT_SUPPORTED); 121 stdErrors.add(OIDCError.REQUEST_NOT_SUPPORTED); 122 stdErrors.add(OIDCError.REQUEST_URI_NOT_SUPPORTED); 123 } 124 125 126 /** 127 * Gets the standard errors for an OpenID Connect authentication error 128 * response. 129 * 130 * @return The standard errors, as a read-only set. 131 */ 132 public static Set<ErrorObject> getStandardErrors() { 133 134 return Collections.unmodifiableSet(stdErrors); 135 } 136 137 138 /** 139 * Creates a new OpenID Connect authentication error response. 140 * 141 * @param redirectURI The base redirection URI. Must not be 142 * {@code null}. 143 * @param error The error. Should match one of the 144 * {@link #getStandardErrors standard errors} for an 145 * OpenID Connect authentication error response. 146 * Must not be {@code null}. 147 * @param state The state, {@code null} if not requested. 148 * @param rm The implied response mode, {@code null} if 149 * unknown. 150 */ 151 public AuthenticationErrorResponse(final URI redirectURI, 152 final ErrorObject error, 153 final State state, 154 final ResponseMode rm) { 155 156 super(redirectURI, error, state, rm); 157 } 158 159 160 @Override 161 public AuthenticationSuccessResponse toSuccessResponse() { 162 throw new ClassCastException("Cannot cast to AuthenticationSuccessResponse"); 163 } 164 165 166 @Override 167 public AuthenticationErrorResponse toErrorResponse() { 168 return this; 169 } 170 171 172 /** 173 * Parses an OpenID Connect authentication error response. 174 * 175 * @param redirectURI The base redirection URI. Must not be 176 * {@code null}. 177 * @param params The response parameters to parse. Must not be 178 * {@code null}. 179 * 180 * @return The OpenID Connect authentication error response. 181 * 182 * @throws ParseException If the parameters couldn't be parsed to an 183 * OpenID Connect authentication error response. 184 */ 185 public static AuthenticationErrorResponse parse(final URI redirectURI, 186 final Map<String,String> params) 187 throws ParseException { 188 189 AuthorizationErrorResponse resp = AuthorizationErrorResponse.parse(redirectURI, params); 190 191 return new AuthenticationErrorResponse( 192 resp.getRedirectionURI(), 193 resp.getErrorObject(), 194 resp.getState(), 195 null); 196 } 197 198 199 /** 200 * Parses an OpenID Connect authentication error response. 201 * 202 * <p>Use a relative URI if the host, port and path details are not 203 * known: 204 * 205 * <pre> 206 * URI relUrl = new URI("https:///?error=invalid_request"); 207 * </pre> 208 * 209 * <p>Example URI: 210 * 211 * <pre> 212 * https://client.example.com/cb? 213 * error=invalid_request 214 * &error_description=the%20request%20is%20not%20valid%20or%20malformed 215 * &state=af0ifjsldkj 216 * </pre> 217 * 218 * @param uri The URI to parse. Can be absolute or relative, with a 219 * fragment or query string containing the authorisation 220 * response parameters. Must not be {@code null}. 221 * 222 * @return The OpenID Connect authentication error response. 223 * 224 * @throws ParseException If the URI couldn't be parsed to an OpenID 225 * Connect authentication error response. 226 */ 227 public static AuthenticationErrorResponse parse(final URI uri) 228 throws ParseException { 229 230 AuthorizationErrorResponse resp = AuthorizationErrorResponse.parse(uri); 231 232 return new AuthenticationErrorResponse( 233 resp.getRedirectionURI(), 234 resp.getErrorObject(), 235 resp.getState(), 236 null); 237 } 238 239 240 /** 241 * Parses an OpenID Connect authentication error response from the 242 * specified initial HTTP 302 redirect response generated at the 243 * authorisation endpoint. 244 * 245 * <p>Example HTTP response: 246 * 247 * <pre> 248 * HTTP/1.1 302 Found 249 * Location: https://client.example.com/cb?error=invalid_request&state=af0ifjsldkj 250 * </pre> 251 * 252 * @param httpResponse The HTTP response to parse. Must not be 253 * {@code null}. 254 * 255 * @return The OpenID Connect authentication error response. 256 * 257 * @throws ParseException If the HTTP response couldn't be parsed to an 258 * OpenID Connect authentication error response. 259 */ 260 public static AuthenticationErrorResponse parse(final HTTPResponse httpResponse) 261 throws ParseException { 262 263 AuthorizationErrorResponse resp = AuthorizationErrorResponse.parse(httpResponse); 264 265 return new AuthenticationErrorResponse( 266 resp.getRedirectionURI(), 267 resp.getErrorObject(), 268 resp.getState(), 269 null); 270 } 271 272 273 /** 274 * Parses an OpenID Connect authentication error response from the 275 * specified HTTP request at the client redirection (callback) URI. 276 * Applies to {@code query}, {@code fragment} and {@code form_post} 277 * response modes. 278 * 279 * <p>Example HTTP request (authorisation success): 280 * 281 * <pre> 282 * GET /cb?error=invalid_request&state=af0ifjsldkj HTTP/1.1 283 * Host: client.example.com 284 * </pre> 285 * 286 * @see #parse(HTTPResponse) 287 * 288 * @param httpRequest The HTTP request to parse. Must not be 289 * {@code null}. 290 * 291 * @throws ParseException If the HTTP request couldn't be parsed to an 292 * OpenID Connect authentication error response. 293 */ 294 public static AuthenticationErrorResponse parse(final HTTPRequest httpRequest) 295 throws ParseException { 296 297 final URI baseURI; 298 299 try { 300 baseURI = httpRequest.getURL().toURI(); 301 302 } catch (URISyntaxException e) { 303 throw new ParseException(e.getMessage(), e); 304 } 305 306 if (httpRequest.getQuery() != null) { 307 // For query string and form_post response mode 308 return parse(baseURI, URLUtils.parseParameters(httpRequest.getQuery())); 309 } else if (httpRequest.getFragment() != null) { 310 // For fragment response mode (never available in actual HTTP request from browser) 311 return parse(baseURI, URLUtils.parseParameters(httpRequest.getFragment())); 312 } else { 313 throw new ParseException("Missing URI fragment, query string or post body"); 314 } 315 } 316}