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...&state=af0ifjsldkj"); 139 * </pre> 140 * 141 * <p>Example URI: 142 * 143 * <pre> 144 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&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&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&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}