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}