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