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 Identifier in Authorization
095 *         Response (draft-ietf-oauth-iss-auth-resp-00).
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.UNMET_AUTHENTICATION_REQUIREMENTS);
119                stdErrors.add(OIDCError.REGISTRATION_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                this(redirectURI, error, state, null, rm);
154        }
155
156
157        /**
158         * Creates a new OpenID Connect authentication error response.
159         *
160         * @param redirectURI The base redirection URI. Must not be
161         *                    {@code null}.
162         * @param error       The error. Should match one of the
163         *                    {@link #getStandardErrors standard errors} for an
164         *                    OpenID Connect authentication error response.
165         *                    Must not be {@code null}.
166         * @param state       The state, {@code null} if not requested.
167         * @param issuer      The issuer, {@code null} if not specified.
168         * @param rm          The implied response mode, {@code null} if
169         *                    unknown.
170         */
171        public AuthenticationErrorResponse(final URI redirectURI,
172                                           final ErrorObject error,
173                                           final State state,
174                                           final Issuer issuer,
175                                           final ResponseMode rm) {
176                                          
177                super(redirectURI, error, state, issuer, rm);
178        }
179
180
181        /**
182         * Creates a new JSON Web Token (JWT) secured OpenID Connect
183         * authentication error response.
184         *
185         * @param redirectURI The base redirection URI. Must not be
186         *                    {@code null}.
187         * @param jwtResponse The JWT-secured response. Must not be
188         *                    {@code null}.
189         * @param rm          The implied response mode, {@code null} if
190         *                    unknown.
191         */
192        public AuthenticationErrorResponse(final URI redirectURI,
193                                           final JWT jwtResponse,
194                                           final ResponseMode rm) {
195                                          
196                super(redirectURI, jwtResponse, rm);
197        }
198        
199        
200        @Override
201        public AuthenticationSuccessResponse toSuccessResponse() {
202                throw new ClassCastException("Cannot cast to AuthenticationSuccessResponse");
203        }
204        
205        
206        @Override
207        public AuthenticationErrorResponse toErrorResponse() {
208                return this;
209        }
210        
211        
212        /**
213         * Converts the specified general OAuth 2.0 authorisation error
214         * response instance to an OpenID authentication error instance.
215         *
216         * @param errorResponse The OAuth 2.0 authorisation error response.
217         *                      Must not be {@code null}.
218         *
219         * @return The OpenID authentication error instance.
220         */
221        private static AuthenticationErrorResponse toAuthenticationErrorResponse(final AuthorizationErrorResponse errorResponse) {
222                
223                if (errorResponse.getJWTResponse() != null) {
224                        // JARM
225                        return new AuthenticationErrorResponse(
226                                errorResponse.getRedirectionURI(),
227                                errorResponse.getJWTResponse(),
228                                errorResponse.getResponseMode());
229                }
230                
231                return new AuthenticationErrorResponse(
232                        errorResponse.getRedirectionURI(),
233                        errorResponse.getErrorObject(),
234                        errorResponse.getState(),
235                        errorResponse.getIssuer(),
236                        errorResponse.getResponseMode());
237        }
238        
239        
240        /**
241         * Parses an OpenID Connect authentication error response.
242         *
243         * @param redirectURI The base redirection URI. Must not be
244         *                    {@code null}.
245         * @param params      The response parameters to parse. Must not be 
246         *                    {@code null}.
247         *
248         * @return The OpenID Connect authentication error response.
249         *
250         * @throws ParseException If the parameters couldn't be parsed to an
251         *                        OpenID Connect authentication error response.
252         */
253        public static AuthenticationErrorResponse parse(final URI redirectURI,
254                                                        final Map<String, List<String>> params)
255                throws ParseException {
256
257                return toAuthenticationErrorResponse(AuthorizationErrorResponse.parse(redirectURI, params));
258        }
259
260
261        /**
262         * Parses an OpenID Connect authentication error response.
263         *
264         * <p>Use a relative URI if the host, port and path details are not
265         * known:
266         *
267         * <pre>
268         * URI relUrl = new URI("https:///?error=invalid_request");
269         * </pre>
270         *
271         * <p>Example URI:
272         *
273         * <pre>
274         * https://client.example.com/cb?
275         * error=invalid_request
276         * &amp;error_description=the%20request%20is%20not%20valid%20or%20malformed
277         * &amp;state=af0ifjsldkj
278         * </pre>
279         *
280         * @param uri The URI to parse. Can be absolute or relative, with a
281         *            fragment or query string containing the authorisation
282         *            response parameters. Must not be {@code null}.
283         *
284         * @return The OpenID Connect authentication error response.
285         *
286         * @throws ParseException If the URI couldn't be parsed to an OpenID
287         *                        Connect authentication error response.
288         */
289        public static AuthenticationErrorResponse parse(final URI uri)
290                throws ParseException {
291
292                return toAuthenticationErrorResponse(AuthorizationErrorResponse.parse(uri));
293        }
294
295
296        /**
297         * Parses an OpenID Connect authentication error response from the
298         * specified initial HTTP 302 redirect response generated at the
299         * authorisation endpoint.
300         *
301         * <p>Example HTTP response:
302         *
303         * <pre>
304         * HTTP/1.1 302 Found
305         * Location: https://client.example.com/cb?error=invalid_request&amp;state=af0ifjsldkj
306         * </pre>
307         *
308         * @param httpResponse The HTTP response to parse. Must not be 
309         *                     {@code null}.
310         *
311         * @return The OpenID Connect authentication error response.
312         *
313         * @throws ParseException If the HTTP response couldn't be parsed to an 
314         *                        OpenID Connect authentication error response.
315         */
316        public static AuthenticationErrorResponse parse(final HTTPResponse httpResponse)
317                throws ParseException {
318
319                return toAuthenticationErrorResponse(AuthorizationErrorResponse.parse(httpResponse));
320        }
321
322
323        /**
324         * Parses an OpenID Connect authentication error response from the
325         * specified HTTP request at the client redirection (callback) URI.
326         * Applies to {@code query}, {@code fragment} and {@code form_post}
327         * response modes.
328         *
329         * <p>Example HTTP request (authorisation success):
330         *
331         * <pre>
332         * GET /cb?error=invalid_request&amp;state=af0ifjsldkj HTTP/1.1
333         * Host: client.example.com
334         * </pre>
335         *
336         * @see #parse(HTTPResponse)
337         *
338         * @param httpRequest The HTTP request to parse. Must not be
339         *                    {@code null}.
340         *
341         * @return The authentication error response.
342         *
343         * @throws ParseException If the HTTP request couldn't be parsed to an
344         *                        OpenID Connect authentication error response.
345         */
346        public static AuthenticationErrorResponse parse(final HTTPRequest httpRequest)
347                throws ParseException {
348
349                return parse(httpRequest.getURI(), parseResponseParameters(httpRequest));
350        }
351}