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.op; 019 020 021import java.io.IOException; 022import java.net.MalformedURLException; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026 027import net.jcip.annotations.ThreadSafe; 028 029import com.nimbusds.jose.JOSEException; 030import com.nimbusds.jose.proc.BadJOSEException; 031import com.nimbusds.jose.proc.SecurityContext; 032import com.nimbusds.jose.util.ResourceRetriever; 033import com.nimbusds.jwt.JWT; 034import com.nimbusds.jwt.JWTClaimsSet; 035import com.nimbusds.jwt.JWTParser; 036import com.nimbusds.jwt.proc.JWTProcessor; 037import com.nimbusds.oauth2.sdk.OAuth2Error; 038import com.nimbusds.oauth2.sdk.ParseException; 039import com.nimbusds.oauth2.sdk.util.JWTClaimsSetUtils; 040import com.nimbusds.openid.connect.sdk.AuthenticationRequest; 041 042 043/** 044 * Resolves the final OpenID Connect authentication request by superseding its 045 * parameters with those found in the optional OpenID Connect request object. 046 * The request object is encoded as a JSON Web Token (JWT) and can be specified 047 * directly (inline) using the {@code request} parameter, or by URL using the 048 * {@code request_uri} parameter. 049 * 050 * <p>To process signed and optionally encrypted request objects a 051 * {@link JWTProcessor JWT processor} for the expected JWS / JWE algorithms 052 * must be provided at construction time. 053 * 054 * <p>To fetch OpenID Connect request objects specified by URL a 055 * {@link ResourceRetriever JWT retriever} must be provided, otherwise only 056 * inlined request objects can be processed. 057 * 058 * <p>Related specifications: 059 * 060 * <ul> 061 * <li>OpenID Connect Core 1.0, section 6. 062 * </ul> 063 */ 064@ThreadSafe 065public class AuthenticationRequestResolver<C extends SecurityContext> { 066 067 068 /** 069 * The JWT processor. 070 */ 071 private final JWTProcessor<C> jwtProcessor; 072 073 074 /** 075 * Optional retriever for request objects passed by URL. 076 */ 077 private final ResourceRetriever jwtRetriever; 078 079 080 /** 081 * Creates a new minimal OpenID Connect authentication request 082 * resolver. It will not process OpenID Connect request objects and 083 * will throw a {@link ResolveException} if the authentication request 084 * includes a {@code request} or {@code request_uri} parameter. 085 */ 086 public AuthenticationRequestResolver() { 087 jwtProcessor = null; 088 jwtRetriever = null; 089 } 090 091 092 /** 093 * Creates a new OpenID Connect authentication request resolver that 094 * supports OpenID Connect request objects passed by value (using the 095 * authentication {@code request} parameter). It will throw a 096 * {@link ResolveException} if the authentication request includes a 097 * {@code request_uri} parameter. 098 * 099 * @param jwtProcessor A configured JWT processor providing JWS 100 * validation and optional JWE decryption of the 101 * request objects. Must not be {@code null}. 102 */ 103 public AuthenticationRequestResolver(final JWTProcessor<C> jwtProcessor) { 104 if (jwtProcessor == null) 105 throw new IllegalArgumentException("The JWT processor must not be null"); 106 this.jwtProcessor = jwtProcessor; 107 jwtRetriever = null; 108 } 109 110 111 /** 112 * Creates a new OpenID Connect request object resolver that supports 113 * OpenID Connect request objects passed by value (using the 114 * authentication {@code request} parameter) or by reference (using the 115 * authentication {@code request_uri} parameter). 116 * 117 * @param jwtProcessor A configured JWT processor providing JWS 118 * validation and optional JWE decryption of the 119 * request objects. Must not be {@code null}. 120 * @param jwtRetriever A configured JWT retriever for OpenID Connect 121 * request objects passed by URI. Must not be 122 * {@code null}. 123 */ 124 public AuthenticationRequestResolver(final JWTProcessor<C> jwtProcessor, 125 final ResourceRetriever jwtRetriever) { 126 if (jwtProcessor == null) 127 throw new IllegalArgumentException("The JWT processor must not be null"); 128 this.jwtProcessor = jwtProcessor; 129 130 if (jwtRetriever == null) 131 throw new IllegalArgumentException("The JWT retriever must not be null"); 132 this.jwtRetriever = jwtRetriever; 133 } 134 135 136 /** 137 * Returns the JWT processor. 138 * 139 * @return The JWT processor, {@code null} if not specified. 140 */ 141 public JWTProcessor<C> getJWTProcessor() { 142 143 return jwtProcessor; 144 } 145 146 147 /** 148 * Returns the JWT retriever. 149 * 150 * @return The JWT retriever, {@code null} if not specified. 151 */ 152 public ResourceRetriever getJWTRetriever() { 153 154 return jwtRetriever; 155 } 156 157 158 /** 159 * Reformats the specified JWT claims set to a 160 * {@literal java.util.Map} instance. 161 * 162 * @param claimsSet The JWT claims set to reformat. Must not be 163 * {@code null}. 164 * 165 * @return The JWT claims set as an unmodifiable map of string keys / 166 * string values. 167 * 168 * @deprecated Use {@link JWTClaimsSetUtils#toJWTClaimsSet}. 169 */ 170 @Deprecated 171 public static Map<String,List<String>> reformatClaims(final JWTClaimsSet claimsSet) { 172 173 return JWTClaimsSetUtils.toMultiValuedParameters(claimsSet); 174 } 175 176 177 /** 178 * Resolves the specified OpenID Connect authentication request by 179 * superseding its parameters with those found in the optional OpenID 180 * Connect request object (if any). 181 * 182 * @param request The OpenID Connect authentication request. 183 * Must not be {@code null}. 184 * @param securityContext Optional security context to pass to the JWT 185 * processor, {@code null} if not specified. 186 * 187 * @return The resolved authentication request, or the original 188 * unmodified request if no OpenID Connect request object was 189 * specified. 190 * 191 * @throws ResolveException If the request couldn't be resolved. 192 * @throws JOSEException If an invalid request JWT is found. 193 */ 194 public AuthenticationRequest resolve(final AuthenticationRequest request, 195 final C securityContext) 196 throws ResolveException, JOSEException { 197 198 if (! request.specifiesRequestObject()) { 199 // Return unmodified 200 return request; 201 } 202 203 final JWT jwt; 204 205 if (request.getRequestURI() != null) { 206 207 // Check if request_uri is supported 208 if (jwtRetriever == null || jwtProcessor == null) { 209 throw new ResolveException(OAuth2Error.REQUEST_URI_NOT_SUPPORTED, request); 210 } 211 212 // Download request object 213 try { 214 jwt = JWTParser.parse(jwtRetriever.retrieveResource(request.getRequestURI().toURL()).getContent()); 215 } catch (MalformedURLException e) { 216 throw new ResolveException(OAuth2Error.INVALID_REQUEST_URI.setDescription("Malformed URL"), request); 217 } catch (IOException e) { 218 // Most likely client problem, possible causes: bad URL, timeout, network down 219 throw new ResolveException("Couldn't retrieve request_uri: " + e.getMessage(), 220 "Network error, check the request_uri", // error_description for client, hide details 221 request, e); 222 } catch (java.text.ParseException e) { 223 throw new ResolveException(OAuth2Error.INVALID_REQUEST_URI.setDescription("Invalid JWT"), request); 224 } 225 226 } else { 227 // Check if request by value is supported 228 if (jwtProcessor == null) { 229 throw new ResolveException(OAuth2Error.REQUEST_NOT_SUPPORTED, request); 230 } 231 232 // Request object inlined 233 jwt = request.getRequestObject(); 234 } 235 236 final JWTClaimsSet jwtClaims; 237 238 try { 239 jwtClaims = jwtProcessor.process(jwt, securityContext); 240 } catch (BadJOSEException e) { 241 throw new ResolveException("Invalid request object: " + e.getMessage(), 242 "Bad JWT / signature / HMAC / encryption", // error_description for client, hide details 243 request, e); 244 } 245 246 Map<String,List<String>> finalParams = new HashMap<>(); 247 finalParams.putAll(request.toParameters()); 248 finalParams.putAll(JWTClaimsSetUtils.toMultiValuedParameters(jwtClaims)); // Merge params from request object 249 finalParams.remove("request"); // make sure request object is deleted 250 finalParams.remove("request_uri"); // make sure request_uri is deleted 251 252 // Create new updated OpenID auth request 253 try { 254 return AuthenticationRequest.parse(request.getEndpointURI(), finalParams); 255 } catch (ParseException e) { 256 // E.g. missing OIDC required redirect_uri 257 throw new ResolveException("Couldn't create final OpenID authentication request: " + e.getMessage(), 258 "Invalid request object parameter(s): " + e.getMessage(), // error_description for client 259 request, e); 260 } 261 } 262}