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-2015 ForgeRock AS. 015 */ 016 017package org.forgerock.services.context; 018 019import static java.util.Arrays.asList; 020 021import java.io.ByteArrayInputStream; 022import java.io.UnsupportedEncodingException; 023import java.security.cert.Certificate; 024import java.security.cert.CertificateEncodingException; 025import java.security.cert.CertificateException; 026import java.security.cert.CertificateFactory; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.List; 030 031import org.forgerock.json.JsonValue; 032import org.forgerock.util.encode.Base64; 033 034/** 035 * Client context gives easy access to client-related information that are available into the request. 036 * Supported data includes: 037 * <ul> 038 * <li>Remote IP address</li> 039 * <li>Remote port</li> 040 * <li>Username</li> 041 * <li>Client provided certificates</li> 042 * <li>User-Agent information</li> 043 * <li>Whether the client is external</li> 044 * <li>Whether the connection to the client is secure</li> 045 * <li>Local port</li> 046 * <li>Local address</li> 047 * </ul> 048 */ 049public final class ClientContext extends AbstractContext { 050 051 // Persisted attribute names 052 private static final String ATTR_REMOTE_USER = "remoteUser"; 053 private static final String ATTR_REMOTE_ADDRESS = "remoteAddress"; 054 private static final String ATTR_REMOTE_PORT = "remotePort"; 055 private static final String ATTR_CERTIFICATES = "certificates"; 056 057 private static final String ATTR_USER_AGENT = "userAgent"; 058 private static final String ATTR_IS_SECURE = "isSecure"; 059 private static final String ATTR_IS_EXTERNAL = "isExternal"; 060 061 private static final String X509_TYPE = "X.509"; 062 private static final String BEGIN_CERTIFICATE = "-----BEGIN CERTIFICATE-----"; 063 private static final String END_CERTIFICATE = "-----END CERTIFICATE-----"; 064 065 private static final String ATTR_LOCAL_ADDRESS = "localAddress"; 066 private static final String ATTR_LOCAL_PORT = "localPort"; 067 068 /** Builder for creating {@code ClientContext} instances. */ 069 public final static class Builder { 070 private final Context parent; 071 private String remoteUser = ""; 072 private String remoteAddress = ""; 073 private int remotePort = -1; 074 private List<? extends Certificate> certificates = Collections.emptyList(); 075 private String userAgent = ""; 076 private boolean isSecure; 077 private String localAddress = ""; 078 private int localPort = -1; 079 080 private Builder(Context parent) { 081 this.parent = parent; 082 } 083 084 /** 085 * Sets the client's remote user. 086 * 087 * @param remoteUser The remote user. 088 * @return The builder instance. 089 */ 090 public Builder remoteUser(String remoteUser) { 091 this.remoteUser = remoteUser; 092 return this; 093 } 094 095 /** 096 * Sets the client's remote address. 097 * 098 * @param remoteAddress The remove address. 099 * @return The builder instance. 100 */ 101 public Builder remoteAddress(String remoteAddress) { 102 this.remoteAddress = remoteAddress; 103 return this; 104 } 105 106 /** 107 * Sets the client's remote port. 108 * 109 * @param remotePort The remote port. 110 * @return The builder instance. 111 */ 112 public Builder remotePort(int remotePort) { 113 this.remotePort = remotePort; 114 return this; 115 } 116 117 /** 118 * Sets the client's certificates. 119 * 120 * @param certificates The list of certificates. 121 * @return The builder instance. 122 * @see #certificates(List) 123 */ 124 public Builder certificates(Certificate... certificates) { 125 if (certificates != null) { 126 return certificates(asList(certificates)); 127 } else { 128 return certificates(Collections.<Certificate>emptyList()); 129 } 130 } 131 132 /** 133 * Sets the client's certificates. 134 * 135 * @param certificates The {@code List} of certificates. 136 * @return The builder instance. 137 * @see #certificates(Certificate...) 138 */ 139 public Builder certificates(List<Certificate> certificates) { 140 this.certificates = certificates; 141 return this; 142 } 143 144 /** 145 * Sets the client's user agent. 146 * 147 * @param userAgent The user agent. 148 * @return The builder instance. 149 */ 150 public Builder userAgent(String userAgent) { 151 this.userAgent = userAgent; 152 return this; 153 } 154 155 /** 156 * Sets whether if the client connection is secure. 157 * @param isSecure {@code true} if the client connection is secure, {@code false} otherwise. 158 * @return The builder instance. 159 */ 160 public Builder secure(boolean isSecure) { 161 this.isSecure = isSecure; 162 return this; 163 } 164 165 /** 166 * Sets the local server's address. 167 * 168 * @param localAddress The local address. 169 * @return The builder instance. 170 */ 171 public Builder localAddress(String localAddress) { 172 this.localAddress = localAddress; 173 return this; 174 } 175 176 /** 177 * Sets the local server's port. 178 * 179 * @param localPort The local port. 180 * @return The builder instance. 181 */ 182 public Builder localPort(int localPort) { 183 this.localPort = localPort; 184 return this; 185 } 186 187 /** 188 * Creates a {@link ClientContext} instance from the specified properties. 189 * 190 * @return A {@link ClientContext} instance. 191 */ 192 public ClientContext build() { 193 if (certificates == null) { 194 certificates = Collections.<Certificate>emptyList(); 195 } 196 return new ClientContext(parent, remoteUser, remoteAddress, remotePort, certificates, userAgent, true, 197 isSecure, localAddress, localPort); 198 } 199 200 } 201 202 /** 203 * Creates a {@link ClientContext.Builder} for creating an external {@link ClientContext} instance. 204 * 205 * @param parent 206 * The parent context. 207 * @return A builder for an external {@code ClientContext} instance. 208 */ 209 public static Builder buildExternalClientContext(Context parent) { 210 return new Builder(parent); 211 } 212 213 /** 214 * Creates an internal {@link ClientContext} instance. 215 * All data related to external context (e.g remote address, user agent...) will be set with empty non null values. 216 * The returned internal {@link ClientContext} is considered as secure. 217 * 218 * @param parent 219 * The parent context. 220 * @return An internal {@link ClientContext} instance. 221 */ 222 public static ClientContext newInternalClientContext(Context parent) { 223 return new ClientContext(parent, "", "", -1, Collections.<Certificate>emptyList(), "", false, true, "", -1); 224 } 225 226 private final Collection<? extends Certificate> certificates; 227 228 /** 229 * Restore from JSON representation. 230 * 231 * @param savedContext 232 * The JSON representation from which this context's attributes 233 * should be parsed. 234 * @param classLoader 235 * The ClassLoader which can properly resolve the persisted class-name. 236 */ 237 public ClientContext(final JsonValue savedContext, final ClassLoader classLoader) { 238 super(savedContext, classLoader); 239 try { 240 this.certificates = Collections.unmodifiableCollection( 241 CertificateFactory.getInstance(X509_TYPE).generateCertificates( 242 new ByteArrayInputStream(data.get(ATTR_CERTIFICATES).asString().getBytes("UTF8")))); 243 } catch (CertificateException | UnsupportedEncodingException e) { 244 throw new IllegalStateException("Unable to deserialize certificates", e); 245 } 246 } 247 248 249 private ClientContext(Context parent, 250 String remoteUser, 251 String remoteAddress, 252 int remotePort, 253 List<? extends Certificate> certificates, 254 String userAgent, 255 boolean isExternal, 256 boolean isSecure, 257 String localAddress, 258 int localPort) { 259 super(parent, "client"); 260 // Maintain the real list of certificates for Java API 261 this.certificates = certificates; 262 263 data.put(ATTR_REMOTE_USER, remoteUser); 264 data.put(ATTR_REMOTE_ADDRESS, remoteAddress); 265 data.put(ATTR_REMOTE_PORT, remotePort); 266 data.put(ATTR_CERTIFICATES, serializeCertificates(certificates)); 267 data.put(ATTR_USER_AGENT, userAgent); 268 data.put(ATTR_IS_EXTERNAL, isExternal); 269 data.put(ATTR_IS_SECURE, isSecure); 270 data.put(ATTR_LOCAL_ADDRESS, localAddress); 271 data.put(ATTR_LOCAL_PORT, localPort); 272 } 273 274 /** Returns Base64-encoded certificates for JSON serialization. */ 275 private String serializeCertificates(final List<? extends Certificate> certificates) { 276 final StringBuilder builder = new StringBuilder(); 277 for (final Certificate certificate : certificates) { 278 try { 279 builder.append(BEGIN_CERTIFICATE) 280 .append(Base64.encode(certificate.getEncoded())) 281 .append(END_CERTIFICATE); 282 } catch (CertificateEncodingException e) { 283 throw new IllegalStateException("Unable to serialize certificates", e); 284 } 285 } 286 return builder.toString(); 287 } 288 289 /** 290 * Returns the login of the user making this request or an empty string if not known. 291 * 292 * @return the login of the user making this request or an empty string if not known. 293 */ 294 public String getRemoteUser() { 295 return data.get(ATTR_REMOTE_USER).asString(); 296 } 297 298 /** 299 * Returns the IP address of the client (or last proxy) that sent the request 300 * or an empty string if the client is internal. 301 * 302 * @return the IP address of the client (or last proxy) that sent the request 303 * or an empty string if the client is internal. 304 */ 305 public String getRemoteAddress() { 306 return data.get(ATTR_REMOTE_ADDRESS).asString(); 307 } 308 309 /** 310 * Returns the source port of the client (or last proxy) that sent the request 311 * or {@code -1} if the client is internal. 312 * 313 * @return the source port of the client (or last proxy) that sent the request 314 * or {@code -1} if the client is internal. 315 */ 316 public int getRemotePort() { 317 return data.get(ATTR_REMOTE_PORT).asInteger(); 318 } 319 320 321 /** 322 * Returns the collection (possibly empty) of certificate(s) provided by the client. 323 * If no certificates are available, an empty list is returned. 324 * 325 * @return the collection (possibly empty) of certificate(s) provided by the client. 326 */ 327 public Collection<? extends Certificate> getCertificates() { 328 return certificates; 329 } 330 331 /** 332 * Returns the value of the {@literal User-Agent} HTTP Header (if any, returns an empty string otherwise). 333 * 334 * @return the value of the {@literal User-Agent} HTTP Header (if any, returns an empty string otherwise). 335 */ 336 public String getUserAgent() { 337 return data.get(ATTR_USER_AGENT).asString(); 338 } 339 340 /** 341 * Returns {@code true} if this client is external. 342 * 343 * @return {@code true} if this client is external. 344 */ 345 public boolean isExternal() { 346 return data.get(ATTR_IS_EXTERNAL).asBoolean(); 347 } 348 349 /** 350 * Returns {@code true} if this client connection is secure. 351 * It is the responsibility to the underlying protocol/implementation 352 * to determine whether or not the connection is secure. 353 * For example HTTPS and internal connections are meant to be secure. 354 * 355 * @return {@code true} if this client connection is secure. 356 */ 357 public boolean isSecure() { 358 return data.get(ATTR_IS_SECURE).asBoolean(); 359 } 360 361 /** 362 * Returns the IP address of the interface that received the request. 363 * 364 * @return the IP address of the server that received the request. 365 */ 366 public String getLocalAddress() { 367 return data.get(ATTR_LOCAL_ADDRESS).asString(); 368 } 369 370 /** 371 * Returns the port of the interface that received the request. 372 * 373 * @return the port of the interface that received the request. 374 */ 375 public int getLocalPort() { 376 return data.get(ATTR_LOCAL_PORT).asInteger(); 377 } 378}