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