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