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