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.List;
023import java.util.Map;
024
025import com.nimbusds.jwt.JWTClaimsSet;
026import com.nimbusds.oauth2.sdk.AuthorizationResponse;
027import com.nimbusds.oauth2.sdk.ParseException;
028import com.nimbusds.oauth2.sdk.http.HTTPRequest;
029import com.nimbusds.oauth2.sdk.http.HTTPResponse;
030import com.nimbusds.oauth2.sdk.jarm.JARMUtils;
031import com.nimbusds.oauth2.sdk.jarm.JARMValidator;
032import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils;
033import com.nimbusds.oauth2.sdk.util.StringUtils;
034import com.nimbusds.oauth2.sdk.util.URIUtils;
035
036
037/**
038 * Parser of OpenID Connect authentication response messages.
039 *
040 * <p>Related specifications:
041 *
042 * <ul>
043 *     <li>OpenID Connect Core 1.0, sections 3.1.2.5. and 3.1.2.6.
044 *     <li>OAuth 2.0 (RFC 6749), section 3.1.
045 *     <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0.
046 *     <li>OAuth 2.0 Form Post Response Mode 1.0.
047 *     <li>Financial-grade API: JWT Secured Authorization Response Mode for
048 *         OAuth 2.0 (JARM).
049 * </ul>
050 */
051public class AuthenticationResponseParser {
052
053
054        /**
055         * Parses an OpenID Connect authentication response.
056         *
057         * @param redirectURI The base redirection URI. Must not be
058         *                    {@code null}.
059         * @param params      The response parameters to parse. Must not be 
060         *                    {@code null}.
061         *
062         * @return The OpenID Connect authentication success or error response.
063         *
064         * @throws ParseException If the parameters couldn't be parsed to an
065         *                        OpenID Connect authentication response.
066         */
067        public static AuthenticationResponse parse(final URI redirectURI,
068                                                   final Map<String,List<String>> params)
069                throws ParseException {
070                
071                return parse(redirectURI, params, null);
072        }
073
074
075        /**
076         * Parses an OpenID Connect authentication response which may be
077         * JSON Web Token (JWT) secured.
078         *
079         * @param redirectURI   The base redirection URI. Must not be
080         *                      {@code null}.
081         * @param params        The response parameters to parse. Must not be
082         *                      {@code null}.
083         * @param jarmValidator The validator of JSON Web Token (JWT) secured
084         *                      authorisation responses (JARM), {@code null} if
085         *                      a plain response is expected.
086         *
087         * @return The OpenID Connect authentication success or error response.
088         *
089         * @throws ParseException If the parameters couldn't be parsed to an
090         *                        OpenID Connect authentication response, or if
091         *                        validation of the JWT response failed.
092         */
093        public static AuthenticationResponse parse(final URI redirectURI,
094                                                   final Map<String,List<String>> params,
095                                                   final JARMValidator jarmValidator)
096                throws ParseException {
097                
098                Map<String,List<String>> workParams = params;
099                
100                String jwtResponseString = MultivaluedMapUtils.getFirstValue(params, "response");
101                
102                if (jarmValidator != null) {
103                        if (StringUtils.isBlank(jwtResponseString)) {
104                                throw new ParseException("Missing JWT-secured (JARM) authorization response parameter");
105                        }
106                        try {
107                                JWTClaimsSet jwtClaimsSet = jarmValidator.validate(jwtResponseString);
108                                workParams = JARMUtils.toMultiValuedStringParameters(jwtClaimsSet);
109                        } catch (Exception e) {
110                                throw new ParseException("Invalid JWT-secured (JARM) authorization response: " + e.getMessage());
111                        }
112                }
113                
114                if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(workParams, "error"))) {
115                        return AuthenticationErrorResponse.parse(redirectURI, workParams);
116                } else if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(workParams, "response"))) {
117                        // JARM that wasn't validated, peek into JWT if signed only
118                        boolean likelyError = JARMUtils.impliesAuthorizationErrorResponse(jwtResponseString);
119                        if (likelyError) {
120                                return AuthenticationErrorResponse.parse(redirectURI, workParams);
121                        } else {
122                                return AuthenticationSuccessResponse.parse(redirectURI, workParams);
123                        }
124                        
125                } else {
126                        return AuthenticationSuccessResponse.parse(redirectURI, workParams);
127                }
128        }
129
130
131        /**
132         * Parses an OpenID Connect authentication response.
133         *
134         * <p>Use a relative URI if the host, port and path details are not
135         * known:
136         *
137         * <pre>
138         * URI relUrl = new URI("https:///?code=Qcb0Orv1...&amp;state=af0ifjsldkj");
139         * </pre>
140         *
141         * <p>Example URI:
142         *
143         * <pre>
144         * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
145         * </pre>
146         *
147         * @param uri The URI to parse. Can be absolute or relative, with a
148         *            fragment or query string containing the authentication
149         *            response parameters. Must not be {@code null}.
150         *
151         * @return The OpenID Connect authentication success or error response.
152         *
153         * @throws ParseException If the redirection URI couldn't be parsed to
154         *                        an OpenID Connect authentication response.
155         */
156        public static AuthenticationResponse parse(final URI uri)
157                throws ParseException {
158
159                return parse(URIUtils.getBaseURI(uri), AuthorizationResponse.parseResponseParameters(uri));
160        }
161
162
163        /**
164         * Parses and validates a JSON Web Token (JWT) secured OpenID Connect
165         * authentication response.
166         *
167         * <p>Use a relative URI if the host, port and path details are not
168         * known:
169         *
170         * <pre>
171         * URI relUrl = new URI("https:///?response=eyJhbGciOiJSUzI1NiIsI...");
172         * </pre>
173         *
174         * @param uri           The URI to parse. Can be absolute or relative,
175         *                      with a fragment or query string containing the
176         *                      authentication response parameters. Must not be
177         *                      {@code null}.
178         * @param jarmValidator The validator of JSON Web Token (JWT) secured
179         *                      authorisation responses (JARM). Must not be
180         *                      {@code null}.
181         *
182         * @return The OpenID Connect authentication success or error response.
183         *
184         * @throws ParseException If the redirection URI couldn't be parsed to
185         *                        an OpenID Connect authentication response or
186         *                        if validation of the JWT response failed.
187         */
188        public static AuthenticationResponse parse(final URI uri,
189                                                   final JARMValidator jarmValidator)
190                throws ParseException {
191                
192                if (jarmValidator == null) {
193                        throw new IllegalArgumentException("The JARM validator must not be null");
194                }
195
196                return parse(URIUtils.getBaseURI(uri), AuthorizationResponse.parseResponseParameters(uri), jarmValidator);
197        }
198        
199        
200        /**
201         * Parses an OpenID Connect authentication response from the specified
202         * initial HTTP 302 redirect response output at the authorisation
203         * endpoint.
204         *
205         * <p>Example HTTP response (authorisation success):
206         *
207         * <pre>
208         * HTTP/1.1 302 Found
209         * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
210         * </pre>
211         *
212         * @param httpResponse The HTTP response to parse. Must not be
213         *                     {@code null}.
214         *
215         * @return The OpenID Connect authentication response.
216         *
217         * @throws ParseException If the HTTP response couldn't be parsed to an
218         *                        OpenID Connect authentication response.
219         */
220        public static AuthenticationResponse parse(final HTTPResponse httpResponse)
221                throws ParseException {
222
223                URI location = httpResponse.getLocation();
224                
225                if (location == null)
226                        throw new ParseException("Missing redirection URI / HTTP Location header");
227
228                return parse(location);
229        }
230        
231        
232        /**
233         * Parses and validates a JSON Web Token (JWT) secured OpenID Connect
234         * authentication response from the specified initial HTTP 302 redirect
235         * response output at the authorisation endpoint.
236         *
237         * <p>Example HTTP response (authorisation success):
238         *
239         * <pre>
240         * HTTP/1.1 302 Found
241         * Location: https://client.example.com/cb?response=eyJhbGciOiJSUzI1...
242         * </pre>
243         *
244         * @param httpResponse  The HTTP response to parse. Must not be
245         *                      {@code null}.
246         * @param jarmValidator The validator of JSON Web Token (JWT) secured
247         *                      authorisation responses (JARM). Must not be
248         *                      {@code null}.
249         *
250         * @return The OpenID Connect authentication response.
251         *
252         * @throws ParseException If the HTTP response couldn't be parsed to an
253         *                        OpenID Connect authentication response or if
254         *                        validation of the JWT response failed.
255         */
256        public static AuthenticationResponse parse(final HTTPResponse httpResponse,
257                                                   final JARMValidator jarmValidator)
258                throws ParseException {
259
260                URI location = httpResponse.getLocation();
261                
262                if (location == null)
263                        throw new ParseException("Missing redirection URI / HTTP Location header");
264
265                return parse(location, jarmValidator);
266        }
267        
268        
269        /**
270         * Parses an OpenID Connect authentication response from the specified
271         * HTTP request at the client redirection (callback) URI. Applies to
272         * the {@code query}, {@code fragment} and {@code form_post} response
273         * modes.
274         *
275         * <p>Example HTTP request (authorisation success):
276         *
277         * <pre>
278         * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz HTTP/1.1
279         * Host: client.example.com
280         * </pre>
281         *
282         * @see #parse(HTTPResponse)
283         *
284         * @param httpRequest The HTTP request to parse. Must not be
285         *                    {@code null}.
286         *
287         * @return The OpenID Connect authentication response.
288         *
289         * @throws ParseException If the HTTP request couldn't be parsed to an
290         *                        OpenID Connect authentication response.
291         */
292        public static AuthenticationResponse parse(final HTTPRequest httpRequest)
293                throws ParseException {
294                
295                return parse(httpRequest.getURI(), AuthorizationResponse.parseResponseParameters(httpRequest));
296        }
297        
298        
299        /**
300         * Parses and validates a JSON Web Token (JWT) secured OpenID Connect
301         * authentication response from the specified HTTP request at the
302         * client redirection (callback) URI. Applies to the {@code query.jwt},
303         * {@code fragment.jwt} and {@code form_post.jwt} response modes.
304         *
305         * <p>Example HTTP request (authorisation success):
306         *
307         * <pre>
308         * GET /cb?response=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... HTTP/1.1
309         * Host: client.example.com
310         * </pre>
311         *
312         * @see #parse(HTTPResponse)
313         *
314         * @param httpRequest   The HTTP request to parse. Must not be
315         *                      {@code null}.
316         * @param jarmValidator The validator of JSON Web Token (JWT) secured
317         *                      authorisation responses (JARM). Must not be
318         *                      {@code null}.
319         *
320         * @return The OpenID Connect authentication response.
321         *
322         * @throws ParseException If the HTTP request couldn't be parsed to an
323         *                        OpenID Connect authentication response or if
324         *                        validation of the JWT response failed.
325         */
326        public static AuthenticationResponse parse(final HTTPRequest httpRequest,
327                                                   final JARMValidator jarmValidator)
328                throws ParseException {
329                
330                if (jarmValidator == null) {
331                        throw new IllegalArgumentException("The JARM validator must not be null");
332                }
333                
334                return parse(httpRequest.getURI(), AuthorizationResponse.parseResponseParameters(httpRequest), jarmValidator);
335        }
336        
337        
338        /**
339         * Prevents public instantiation.
340         */
341        private AuthenticationResponseParser() { }
342}