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