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