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>OAuth 2.0 (RFC 6749), sections 4.1.2.1 and 4.2.2.1.
087 *     <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0.
088 *     <li>OAuth 2.0 Form Post Response Mode 1.0.
089 *     <li>Financial-grade API: JWT Secured Authorization Response Mode for
090 *         OAuth 2.0 (JARM).
091 * </ul>
092 */
093@Immutable
094public class AuthenticationErrorResponse
095        extends AuthorizationErrorResponse
096        implements AuthenticationResponse {
097
098
099        /**
100         * The standard errors for an OpenID Connect authentication error
101         * response.
102         */
103        private static final Set<ErrorObject> stdErrors = new HashSet<>();
104        
105        
106        static {
107                stdErrors.addAll(AuthorizationErrorResponse.getStandardErrors());
108
109                stdErrors.add(OIDCError.INTERACTION_REQUIRED);
110                stdErrors.add(OIDCError.LOGIN_REQUIRED);
111                stdErrors.add(OIDCError.ACCOUNT_SELECTION_REQUIRED);
112                stdErrors.add(OIDCError.CONSENT_REQUIRED);
113                stdErrors.add(OAuth2Error.INVALID_REQUEST_URI);
114                stdErrors.add(OAuth2Error.INVALID_REQUEST_OBJECT);
115                stdErrors.add(OIDCError.REGISTRATION_NOT_SUPPORTED);
116                stdErrors.add(OAuth2Error.REQUEST_NOT_SUPPORTED);
117                stdErrors.add(OAuth2Error.REQUEST_URI_NOT_SUPPORTED);
118        }
119
120
121        /**
122         * Gets the standard errors for an OpenID Connect authentication error
123         * response.
124         *
125         * @return The standard errors, as a read-only set.
126         */
127        public static Set<ErrorObject> getStandardErrors() {
128        
129                return Collections.unmodifiableSet(stdErrors);
130        }
131
132
133        /**
134         * Creates a new OpenID Connect authentication error response.
135         *
136         * @param redirectURI The base redirection URI. Must not be
137         *                    {@code null}.
138         * @param error       The error. Should match one of the 
139         *                    {@link #getStandardErrors standard errors} for an 
140         *                    OpenID Connect authentication error response.
141         *                    Must not be {@code null}.
142         * @param state       The state, {@code null} if not requested.
143         * @param rm          The implied response mode, {@code null} if
144         *                    unknown.
145         */
146        public AuthenticationErrorResponse(final URI redirectURI,
147                                           final ErrorObject error,
148                                           final State state,
149                                           final ResponseMode rm) {
150                                          
151                super(redirectURI, error, state, rm);
152        }
153
154
155        /**
156         * Creates a new JSON Web Token (JWT) secured OpenID Connect
157         * authentication error response.
158         *
159         * @param redirectURI The base redirection URI. Must not be
160         *                    {@code null}.
161         * @param jwtResponse The JWT-secured response. Must not be
162         *                    {@code null}.
163         * @param rm          The implied response mode, {@code null} if
164         *                    unknown.
165         */
166        public AuthenticationErrorResponse(final URI redirectURI,
167                                           final JWT jwtResponse,
168                                           final ResponseMode rm) {
169                                          
170                super(redirectURI, jwtResponse, rm);
171        }
172        
173        
174        @Override
175        public AuthenticationSuccessResponse toSuccessResponse() {
176                throw new ClassCastException("Cannot cast to AuthenticationSuccessResponse");
177        }
178        
179        
180        @Override
181        public AuthenticationErrorResponse toErrorResponse() {
182                return this;
183        }
184        
185        
186        /**
187         * Converts the specified general OAuth 2.0 authorisation error
188         * response instance to an OpenID authentication error instance.
189         *
190         * @param errorResponse The OAuth 2.0 authorisation error response.
191         *                      Must not be {@code null}.
192         *
193         * @return The OpenID authentication error instance.
194         */
195        private static AuthenticationErrorResponse toAuthenticationErrorResponse(final AuthorizationErrorResponse errorResponse) {
196                
197                if (errorResponse.getJWTResponse() != null) {
198                        // JARM
199                        return new AuthenticationErrorResponse(
200                                errorResponse.getRedirectionURI(),
201                                errorResponse.getJWTResponse(),
202                                errorResponse.getResponseMode());
203                }
204                
205                return new AuthenticationErrorResponse(
206                        errorResponse.getRedirectionURI(),
207                        errorResponse.getErrorObject(),
208                        errorResponse.getState(),
209                        errorResponse.getResponseMode());
210        }
211        
212        
213        /**
214         * Parses an OpenID Connect authentication error response.
215         *
216         * @param redirectURI The base redirection URI. Must not be
217         *                    {@code null}.
218         * @param params      The response parameters to parse. Must not be 
219         *                    {@code null}.
220         *
221         * @return The OpenID Connect authentication error response.
222         *
223         * @throws ParseException If the parameters couldn't be parsed to an
224         *                        OpenID Connect authentication error response.
225         */
226        public static AuthenticationErrorResponse parse(final URI redirectURI,
227                                                        final Map<String, List<String>> params)
228                throws ParseException {
229
230                return toAuthenticationErrorResponse(AuthorizationErrorResponse.parse(redirectURI, params));
231        }
232
233
234        /**
235         * Parses an OpenID Connect authentication error response.
236         *
237         * <p>Use a relative URI if the host, port and path details are not
238         * known:
239         *
240         * <pre>
241         * URI relUrl = new URI("https:///?error=invalid_request");
242         * </pre>
243         *
244         * <p>Example URI:
245         *
246         * <pre>
247         * https://client.example.com/cb?
248         * error=invalid_request
249         * &amp;error_description=the%20request%20is%20not%20valid%20or%20malformed
250         * &amp;state=af0ifjsldkj
251         * </pre>
252         *
253         * @param uri The URI to parse. Can be absolute or relative, with a
254         *            fragment or query string containing the authorisation
255         *            response parameters. Must not be {@code null}.
256         *
257         * @return The OpenID Connect authentication error response.
258         *
259         * @throws ParseException If the URI couldn't be parsed to an OpenID
260         *                        Connect authentication error response.
261         */
262        public static AuthenticationErrorResponse parse(final URI uri)
263                throws ParseException {
264
265                return toAuthenticationErrorResponse(AuthorizationErrorResponse.parse(uri));
266        }
267
268
269        /**
270         * Parses an OpenID Connect authentication error response from the
271         * specified initial HTTP 302 redirect response generated at the
272         * authorisation endpoint.
273         *
274         * <p>Example HTTP response:
275         *
276         * <pre>
277         * HTTP/1.1 302 Found
278         * Location: https://client.example.com/cb?error=invalid_request&amp;state=af0ifjsldkj
279         * </pre>
280         *
281         * @param httpResponse The HTTP response to parse. Must not be 
282         *                     {@code null}.
283         *
284         * @return The OpenID Connect authentication error response.
285         *
286         * @throws ParseException If the HTTP response couldn't be parsed to an 
287         *                        OpenID Connect authentication error response.
288         */
289        public static AuthenticationErrorResponse parse(final HTTPResponse httpResponse)
290                throws ParseException {
291
292                return toAuthenticationErrorResponse(AuthorizationErrorResponse.parse(httpResponse));
293        }
294
295
296        /**
297         * Parses an OpenID Connect authentication error response from the
298         * specified HTTP request at the client redirection (callback) URI.
299         * Applies to {@code query}, {@code fragment} and {@code form_post}
300         * response modes.
301         *
302         * <p>Example HTTP request (authorisation success):
303         *
304         * <pre>
305         * GET /cb?error=invalid_request&amp;state=af0ifjsldkj HTTP/1.1
306         * Host: client.example.com
307         * </pre>
308         *
309         * @see #parse(HTTPResponse)
310         *
311         * @param httpRequest The HTTP request to parse. Must not be
312         *                    {@code null}.
313         *
314         * @return The authentication error response.
315         *
316         * @throws ParseException If the HTTP request couldn't be parsed to an
317         *                        OpenID Connect authentication error response.
318         */
319        public static AuthenticationErrorResponse parse(final HTTPRequest httpRequest)
320                throws ParseException {
321
322                return parse(httpRequest.getURI(), parseResponseParameters(httpRequest));
323        }
324}