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