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