001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2014-2017 ForgeRock AS. 015 */ 016 017package org.forgerock.oauth2.core; 018 019import javax.inject.Inject; 020import java.io.IOException; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Locale; 024import java.util.Map; 025import java.util.Set; 026 027import com.google.inject.assistedinject.Assisted; 028import org.forgerock.guava.common.collect.ClassToInstanceMap; 029import org.forgerock.guava.common.collect.MutableClassToInstanceMap; 030import org.forgerock.json.JsonValue; 031import org.forgerock.openam.rest.representations.JacksonRepresentationFactory; 032import org.restlet.Request; 033import org.restlet.data.Form; 034import org.restlet.data.MediaType; 035import org.restlet.data.Method; 036import org.restlet.ext.jackson.JacksonRepresentation; 037import org.restlet.ext.servlet.ServletUtils; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041/** 042 * An abstraction of the actual request so as to allow the core of the OAuth2 provider to be agnostic of the library 043 * used to translate the HTTP request. 044 * 045 * @since 12.0.0 046 * @supported.all.api 047 */ 048public class OAuth2Request { 049 050 private final ClassToInstanceMap<Token> tokens = MutableClassToInstanceMap.create(); 051 private final Logger logger = LoggerFactory.getLogger("OAuth2Provider"); 052 private final Request request; 053 private final JacksonRepresentationFactory jacksonRepresentationFactory; 054 private String sessionId; 055 private String ops = null; 056 private JsonValue body; 057 058 /** 059 * Constructs a new RestletOAuth2Request. 060 * 061 * @param request The Restlet request. 062 */ 063 @Inject 064 public OAuth2Request(JacksonRepresentationFactory jacksonRepresentationFactory, @Assisted Request request) { 065 this.jacksonRepresentationFactory = jacksonRepresentationFactory; 066 this.request = request; 067 } 068 069 /** 070 * Gets the actual underlying request. 071 * 072 * @return The underlying request. 073 */ 074 public Request getRequest() { 075 return request; 076 } 077 078 /** 079 * Gets the specified parameter from the request. 080 * <br/> 081 * Used to call the overloaded getParameter method with checkQueryParam set to true. 082 * The checkQueryParam enables us to prevent checking the query parameters from the request. 083 * 084 * @param name The name of the parameter. 085 * @param <T> The type of the parameter. 086 * @return The parameter value or null if no parameter found. 087 */ 088 public <T> T getParameter(String name) { 089 return (T) getParameter(name, true); 090 } 091 092 /** 093 * Gets the specified parameter from the request. 094 * <br/> 095 * This method will first check the attributes then query string if checkQueryParam == true, then move onto check 096 * entity values if it is a post. 097 * 098 * @param name The name of the parameter. 099 * @param checkQueryParam boolean value indicating if the query parameter should be checked. 100 * @param <T> The type of the parameter. 101 * @return The parameter value or null if no parameter found. 102 */ 103 public <T> T getParameter(String name, boolean checkQueryParam) { 104 Object value = getAttribute(request, name); 105 if (value != null) { 106 return (T) value; 107 } 108 109 //query param priority over body 110 if ((checkQueryParam == true) && (getQueryParameter(request, name) != null)){ 111 return (T) getQueryParameter(request, name); 112 } 113 114 if (request.getMethod().equals(Method.POST)) { 115 if (request.getEntity() != null) { 116 if (MediaType.APPLICATION_WWW_FORM.equals(request.getEntity().getMediaType())) { 117 Form form = new Form(request.getEntity()); 118 // restore the entity body 119 request.setEntity(form.getWebRepresentation()); 120 return (T) form.getValuesMap().get(name); 121 } else if (MediaType.APPLICATION_JSON.equals(request.getEntity().getMediaType())) { 122 return (T) getBody().get(name).getObject(); 123 } 124 } 125 } 126 return null; 127 } 128 129 /** 130 * Gets the count of the parameter present in the request with the given name 131 * 132 * @param name The name of the parameter 133 * @return The count of the the parameter with the given name 134 */ 135 public int getParameterCount(String name) { 136 return request.getResourceRef().getQueryAsForm().subList(name).size(); 137 } 138 139 /** 140 * 141 * Gets the name of the parameters in the current request 142 * 143 * 144 * @return The parameter names in the request 145 */ 146 public Set<String> getParameterNames() { 147 148 if (request.getMethod().equals(Method.GET)) { 149 return request.getResourceRef().getQueryAsForm().getNames(); 150 } else if (request.getMethod().equals(Method.POST)) { 151 if (request.getEntity() != null) { 152 if (MediaType.APPLICATION_WWW_FORM.equals(request.getEntity().getMediaType())) { 153 Form form = new Form(request.getEntity()); 154 // restore the entity body 155 request.setEntity(form.getWebRepresentation()); 156 return form.getNames(); 157 } else if (MediaType.APPLICATION_JSON.equals(request.getEntity().getMediaType())) { 158 return getBody().keys(); 159 } 160 } 161 } 162 return Collections.emptySet(); 163 } 164 165 /** 166 * Gets the value for an attribute from the request with the specified name. 167 * 168 * @param request The request. 169 * @param name The name. 170 * @return The attribute value, may be {@code null} 171 */ 172 private Object getAttribute(Request request, String name) { 173 final Object value = request.getAttributes().get(name); 174 return value; 175 } 176 177 /** 178 * Gets the value for a query parameter from the request with the specified name. 179 * 180 * @param request The request. 181 * @param name The name. 182 * @return The query parameter value, may be {@code null}. 183 */ 184 private String getQueryParameter(Request request, String name) { 185 return request.getResourceRef().getQueryAsForm().getValuesMap().get(name); 186 } 187 188 /** 189 * Gets the body of the request. 190 * <br/> 191 * Note: reading of the body maybe a one time only operation, so the implementation needs to cache the content 192 * of the body so multiple calls to this method do not behave differently. 193 * <br/> 194 * This method should only ever be called for access and refresh token request and requests to the userinfo and 195 * tokeninfo endpoints. 196 * 197 * @return The body of the request. 198 */ 199 public JsonValue getBody() { 200 if (body == null) { 201 final JacksonRepresentation<Map> representation = 202 jacksonRepresentationFactory.create(request.getEntity(), Map.class); 203 try { 204 body = new JsonValue(representation.getObject()); 205 } catch (IOException e) { 206 logger.error(e.getMessage()); 207 return JsonValue.json(JsonValue.object()); 208 } 209 } 210 return body; 211 } 212 213 /** 214 * Set a Token that is in play for this request. 215 * @param tokenClass The token type. 216 * @param token The token instance. 217 * @param <T> The type of token. 218 */ 219 public <T extends Token> void setToken(Class<T> tokenClass, T token) { 220 tokens.putInstance(tokenClass, token); 221 } 222 223 /** 224 * Get a Token that is in play for this request. 225 * @param tokenClass The token type. 226 * @param <T> The type of token. 227 * @return The token instance. 228 */ 229 public <T extends Token> T getToken(Class<T> tokenClass) { 230 return tokens.getInstance(tokenClass); 231 } 232 233 /** 234 * Get all the tokens that have been used in this request. 235 * @return The token instances. 236 */ 237 public Collection<Token> getTokens() { 238 return tokens.values(); 239 } 240 241 /** 242 * Sets the user's session for this request. 243 * 244 * @param sessionId The user's session. 245 */ 246 public void setSession(String sessionId) { 247 this.sessionId = sessionId; 248 } 249 250 /** 251 * Gets the user's session for this request. 252 * 253 * @return The user's session. 254 */ 255 public String getSession() { 256 return sessionId; 257 } 258 259 /** 260 * Sets the user's session public reference for this request. 261 * 262 * @param ops The user's session public reference. 263 */ 264 public void setOps(String ops) { 265 this.ops = ops; 266 } 267 268 /** 269 * Gets the user's session public reference for this request. 270 * 271 * @return The user's session public reference. 272 */ 273 public String getOps() { 274 return ops; 275 } 276 277 /** 278 * Get the request locale. 279 * @return The Locale object. 280 */ 281 public Locale getLocale() { 282 return ServletUtils.getRequest(request).getLocale(); 283 } 284 285 /** 286 * Creates an {@code OAuth2Request} which holds the provided realm only. 287 * 288 * @param realm The request realm. 289 * @return An {@code OAuth2Request}. 290 */ 291 public static OAuth2Request forRealm(String realm) { 292 return new RealmOnlyOAuth2Request(realm); 293 } 294 295 private static class RealmOnlyOAuth2Request extends OAuth2Request { 296 297 private final String realm; 298 299 private RealmOnlyOAuth2Request(String realm) { 300 super(null, null); 301 this.realm = realm; 302 } 303 304 @Override 305 public Request getRequest() { 306 throw new UnsupportedOperationException("Realm parameter only OAuth2Request"); 307 } 308 309 @Override 310 public <T> T getParameter(String name) { 311 if ("realm".equals(name)) { 312 return (T) realm; 313 } 314 throw new UnsupportedOperationException("Realm parameter only OAuth2Request"); 315 } 316 317 @Override 318 public JsonValue getBody() { 319 return null; 320 } 321 322 @Override 323 public int getParameterCount(String name) { throw new UnsupportedOperationException(); } 324 325 @Override 326 public Set<String> getParameterNames() { throw new UnsupportedOperationException(); } 327 328 @Override 329 public java.util.Locale getLocale() { 330 throw new UnsupportedOperationException(); 331 } 332 } 333}