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