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 2015-2016 ForgeRock AS.
015 */
016package org.forgerock.audit.events;
017
018import static org.forgerock.json.JsonValue.field;
019import static org.forgerock.json.JsonValue.json;
020import static org.forgerock.json.JsonValue.object;
021
022import java.util.ArrayList;
023import java.util.LinkedHashMap;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Map;
027import java.util.TreeMap;
028import java.util.concurrent.TimeUnit;
029
030import org.forgerock.http.header.CookieHeader;
031import org.forgerock.http.protocol.Cookie;
032import org.forgerock.json.JsonValue;
033import org.forgerock.json.resource.ActionRequest;
034import org.forgerock.json.resource.Request;
035import org.forgerock.services.context.ClientContext;
036import org.forgerock.services.context.Context;
037import org.forgerock.util.Reject;
038import org.forgerock.util.annotations.VisibleForTesting;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042/**
043 * Builder for audit access events.
044 * <p>
045 * This builder should not be used directly but be specialized for each product to allow to define
046 * new specific fields, e.g
047 * <pre>
048 * <code>
049 * class OpenProductAccessAuditEventBuilder{@code <T extends OpenProductAccessAuditEventBuilder<T>>}
050 extends AccessAuditEventBuilder{@code <T>} {
051 *
052 *    protected OpenProductAccessAuditEventBuilder(DnsUtils dnsUtils) {
053 *        super(dnsUtils);
054 *    }
055 *
056 *    public static {@code <T>} OpenProductAccessAuditEventBuilder{@code <?>} productAccessEvent() {
057 *       return new OpenProductAccessAuditEventBuilder(new DnsUtils());
058 *    }
059 *
060 *    public T someField(String v) {
061 *      jsonValue.put("someField", v);
062 *      return self();
063 *    }
064 *
065 *    ...
066 * }
067 * </code>
068 * </pre>
069 *
070 * @param <T> the type of the builder
071 */
072public class AccessAuditEventBuilder<T extends AccessAuditEventBuilder<T>> extends AuditEventBuilder<T> {
073
074    /** The server event payload field name. */
075    public static final String SERVER = "server";
076    /** The client event payload field name. */
077    public static final String CLIENT = "client";
078    /** The IP event payload field name. */
079    public static final String IP = "ip";
080    /** The port event payload field name. */
081    public static final String PORT = "port";
082    /** The request event payload field name. */
083    public static final String REQUEST = "request";
084    /** The protocol event payload field name. */
085    public static final String PROTOCOL = "protocol";
086    /** The operation event payload field name. */
087    public static final String OPERATION = "operation";
088    /** The secure event payload field name. */
089    public static final String SECURE = "secure";
090    /** The method event payload field name. */
091    public static final String METHOD = "method";
092    /** The detail event payload field name. */
093    public static final String DETAIL = "detail";
094    /** The path event payload field name. */
095    public static final String PATH = "path";
096    /** The query parameters event payload field name. */
097    public static final String QUERY_PARAMETERS = "queryParameters";
098    /** The headers event payload field name. */
099    public static final String HEADERS = "headers";
100    /** The http event payload field name. */
101    public static final String HTTP = "http";
102    /** The status event payload field name. */
103    public static final String STATUS = "status";
104    /** The status code event payload field name. */
105    public static final String STATUS_CODE = "statusCode";
106    /** The elapsed time event payload field name. */
107    public static final String ELAPSED_TIME = "elapsedTime";
108    /** The elapsed time unit event payload field name. */
109    public static final String ELAPSED_TIME_UNITS = "elapsedTimeUnits";
110    /** The response event payload field name. */
111    public static final String RESPONSE = "response";
112    /** The cookies event payload field name. */
113    public static final String COOKIES = "cookies";
114
115    /** The protocol field CREST value. */
116    public static final String CREST_PROTOCOL = "CREST";
117
118    private static final String HTTP_CONTEXT_NAME = "http";
119    private static final String CLIENT_CONTEXT_NAME = "client";
120
121    private static final Logger logger = LoggerFactory.getLogger(AccessAuditEventBuilder.class);
122
123    private boolean performReverseDnsLookup = false;
124
125    /**
126     * Starts to build an audit access event.
127     * <p>
128     * Note: it is preferable to use a specialized builder that allow to add
129     * fields specific to a product.
130     *
131     * @return an audit access event builder
132     */
133    @SuppressWarnings("rawtypes")
134    public static AccessAuditEventBuilder<?> accessEvent() {
135        return new AccessAuditEventBuilder();
136    }
137
138    /**
139     * Instructs the builder to lookup client.host from client.ip when populating client details.
140     *
141     * @return this builder
142     */
143    public final T withReverseDnsLookup() {
144        performReverseDnsLookup = true;
145        return self();
146    }
147
148    /**
149     * Whether the client.host should be looked up from client.ip.
150     * @return True if so.
151     */
152    protected boolean isReverseDnsLookupEnabled() {
153        return performReverseDnsLookup;
154    }
155
156    /**
157     * Sets the provided server values for the event.
158     *
159     * @param ip the ip of the server.
160     * @param port the port of the server.
161     * @return this builder
162     */
163    public final T server(String ip, int port) {
164        final Object server = object(
165                field(IP, ip),
166                field(PORT, port));
167        jsonValue.put(SERVER, server);
168        return self();
169    }
170
171    /**
172     * Sets the provided client ip and port for the event.
173     *
174     * @param ip the ip of the client.
175     * @param port the port of the client.
176     * @return this builder
177     */
178    public final T client(String ip, int port) {
179        final Object client = object(
180                field(IP, ip),
181                field(PORT, port));
182        jsonValue.put(CLIENT, client);
183        return self();
184    }
185
186    /**
187     * Sets the provided client ip for the event.
188     *
189     * @param ip the ip of the client.
190     * @return this builder
191     */
192    public final T client(String ip) {
193        final Object client = object(
194                field(IP, ip));
195        jsonValue.put(CLIENT, client);
196        return self();
197    }
198
199    /**
200     * Sets the provided request details for the event.
201     *
202     * @param protocol the type of request.
203     * @param operation the type of operation (e.g. CREATE, READ, UPDATE, DELETE, PATCH, ACTION, or QUERY).
204     * @return this builder
205     */
206    public final T request(String protocol, String operation) {
207        final Object request = object(
208                field(PROTOCOL, protocol),
209                field(OPERATION, operation));
210        jsonValue.put(REQUEST, request);
211        return self();
212    }
213
214    /**
215     * Sets the provided request details for the event.
216     *
217     * @param protocol the type of request.
218     * @param operation the type of operation (e.g. CREATE, READ, UPDATE, DELETE, PATCH, ACTION, or QUERY).
219     * @param detail additional details relating to the request (e.g. the ACTION name or summary of the payload).
220     * @return this builder
221     */
222    public final T request(String protocol, String operation, JsonValue detail) {
223        Reject.ifNull(detail);
224        final Object request = object(
225                field(PROTOCOL, protocol),
226                field(OPERATION, operation),
227                field(DETAIL, detail.getObject()));
228        jsonValue.put(REQUEST, request);
229        return self();
230    }
231
232    /**
233     * Sets the provided HTTP request fields for the event.
234     *
235     * @param secure Was the request secure ?
236     * @param method the HTTP method.
237     * @param path the path of HTTP request.
238     * @param queryParameters the query parameters of HTTP request.
239     * @param headers the list of headers of HTTP request. The headers are optional.
240     * @return this builder
241     */
242    public final T httpRequest(boolean secure, String method, String path, Map<String, List<String>> queryParameters,
243            Map<String, List<String>> headers) {
244        List<String> cookiesHeader = headers.remove("Cookie");
245        if (cookiesHeader == null || cookiesHeader.isEmpty()) {
246            cookiesHeader = headers.remove("cookie");
247        }
248        final List<Cookie> listOfCookies = new LinkedList<>();
249        if (cookiesHeader != null && !cookiesHeader.isEmpty()) {
250            listOfCookies.addAll(CookieHeader.valueOf(cookiesHeader.get(0)).getCookies());
251        }
252
253        final Map<String, String> cookies = new LinkedHashMap<>();
254        for (final Cookie cookie : listOfCookies) {
255            cookies.put(cookie.getName(), cookie.getValue());
256        }
257        httpRequest(secure, method, path, queryParameters, headers, cookies);
258        return self();
259    }
260
261    /**
262     * Sets the provided HTTP request fields for the event.
263     *
264     * @param secure Was the request secure ?
265     * @param method the HTTP method.
266     * @param path the path of HTTP request.
267     * @param queryParameters the query parameters of HTTP request.
268     * @param headers the list of headers of HTTP request. The headers are optional.
269     * @param cookies the list of cookies of HTTP request. The cookies are optional.
270     * @return this builder
271     */
272    public final T httpRequest(boolean secure, String method, String path,  Map<String, List<String>> queryParameters,
273            Map<String, List<String>> headers, Map<String, String> cookies) {
274        final Object httpRequest = object(
275                field(SECURE, secure),
276                field(METHOD, method),
277                field(PATH, path),
278                field(QUERY_PARAMETERS, queryParameters),
279                field(HEADERS, headers),
280                field(COOKIES, cookies));
281        getOrCreateHttp().put(REQUEST, httpRequest);
282        return self();
283    }
284
285    /**
286     * Sets the provided HTTP fields for the event.
287     *
288     * @param headers the list of headers of HTTP response. The headers are optional.
289     * @return this builder
290     */
291    public final T httpResponse(Map<String, List<String>> headers) {
292        final Object httpResponse = object(field(HEADERS, headers));
293        getOrCreateHttp().put(RESPONSE, httpResponse);
294        return self();
295    }
296
297    @VisibleForTesting
298    JsonValue getOrCreateHttp() {
299        if (jsonValue.get(HTTP).isNull()) {
300            jsonValue.put(HTTP, object());
301        }
302        return jsonValue.get(HTTP);
303    }
304
305    @VisibleForTesting
306    JsonValue getOrCreateHttpResponse() {
307        if (getOrCreateHttp().get(RESPONSE).isNull()) {
308            getOrCreateHttp().put(RESPONSE, object());
309        }
310        return getOrCreateHttp().get(RESPONSE);
311    }
312
313    @VisibleForTesting
314    JsonValue getOrCreateHttpResponseCookies() {
315        final JsonValue httpResponse = getOrCreateHttpResponse();
316        JsonValue cookies = httpResponse.get(COOKIES);
317        if (cookies.isNull()) {
318            httpResponse.put(COOKIES, new ArrayList());
319        }
320        cookies = httpResponse.get(COOKIES);
321        return cookies;
322    }
323
324    /**
325     * Sets the provided response for the event.
326     *
327     * @param status the status of the operation.
328     * @param statusCode the status code of the operation.
329     * @param elapsedTime the execution time of the action.
330     * @param elapsedTimeUnits the unit of measure for the execution time value.
331     * @return this builder
332     */
333    public final T response(ResponseStatus status, String statusCode, long elapsedTime, TimeUnit elapsedTimeUnits) {
334        final Object response = object(
335                field(STATUS, status == null ? null : status.toString()),
336                field(STATUS_CODE, statusCode),
337                field(ELAPSED_TIME, elapsedTime),
338                field(ELAPSED_TIME_UNITS, elapsedTimeUnits == null ? null : elapsedTimeUnits.name()));
339        jsonValue.put(RESPONSE, response);
340        return self();
341    }
342
343    /**
344     * Sets the provided response for the event, with an additional detail.
345     *
346     * @param status the status of the operation.
347     * @param statusCode the status code of the operation.
348     * @param elapsedTime the execution time of the action.
349     * @param elapsedTimeUnits the unit of measure for the execution time value.
350     * @param detail additional details relating to the response (e.g. failure description or summary of the payload).
351     * @return this builder
352     */
353    public final T responseWithDetail(ResponseStatus status, String statusCode,
354            long elapsedTime, TimeUnit elapsedTimeUnits, JsonValue detail) {
355        Reject.ifNull(detail);
356        final Object response = object(
357                field(STATUS, status == null ? null : status.toString()),
358                field(STATUS_CODE, statusCode),
359                field(ELAPSED_TIME, elapsedTime),
360                field(ELAPSED_TIME_UNITS, elapsedTimeUnits == null ? null : elapsedTimeUnits.name()),
361                field(DETAIL, detail.getObject()));
362        jsonValue.put(RESPONSE, response);
363        return self();
364    }
365
366    /**
367     * Sets client ip, port and host from <code>ClientContext</code>, if the provided
368     * <code>Context</code> contains a <code>ClientContext</code>.
369     *
370     * @param context The root CHF context.
371     * @return this builder
372     */
373    public final T clientFromContext(Context context) {
374        if (context.containsContext(ClientContext.class)) {
375            ClientContext clientContext = context.asContext(ClientContext.class);
376            client(clientContext.getRemoteAddress(), clientContext.getRemotePort());
377        }
378        return self();
379    }
380
381    /**
382     * Sets the server fields for the event, if the provided
383     * <code>Context</code> contains a <code>ClientContext</code>..
384     *
385     * @param context the CREST context
386     * @return this builder
387     */
388    public final T serverFromContext(Context context) {
389        if (context.containsContext(ClientContext.class)) {
390            ClientContext clientContext = context.asContext(ClientContext.class);
391            server(clientContext.getLocalAddress(), clientContext.getLocalPort());
392        }
393        return self();
394    }
395
396    /**
397     * Sets HTTP method, path, queryString and headers from <code>HttpContext</code>, if the provided
398     * <code>Context</code> contains a <code>HttpContext</code>.
399     *
400     * @param context The CREST context.
401     * @return this builder
402     */
403    public final T httpFromContext(Context context) {
404        if (context.containsContext(HTTP_CONTEXT_NAME)) {
405            final JsonValue httpContext = context.getContext(HTTP_CONTEXT_NAME).toJsonValue();
406            final JsonValue clientContext = context.getContext(CLIENT_CONTEXT_NAME).toJsonValue();
407            httpRequest(clientContext.get("isSecure").asBoolean(),
408                    httpContext.get("method").asString(),
409                    httpContext.get("path").asString(),
410                    asModifiableCaseSensitiveMap(httpContext.get("parameters").asMapOfList(String.class)),
411                    asModifiableCaseInsensitiveMap(httpContext.get("headers").asMapOfList(String.class)));
412        }
413        return self();
414    }
415
416    private <E> Map<String, List<E>> asModifiableCaseSensitiveMap(Map<String, List<E>> map) {
417        return new LinkedHashMap<>(map);
418    }
419
420    private <E> Map<String, List<E>> asModifiableCaseInsensitiveMap(Map<String, List<E>> map) {
421        TreeMap<String, List<E>> caseInsensitiveMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
422        caseInsensitiveMap.putAll(map);
423        return caseInsensitiveMap;
424    }
425
426    /**
427     * Sets request detail from {@link Request}.
428     *
429     * @param request The CREST request.
430     * @return this builder
431     */
432    public final T requestFromCrestRequest(Request request) {
433        final String operation = request.getRequestType().name();
434        if (request instanceof ActionRequest) {
435            final String action = ((ActionRequest) request).getAction();
436            final JsonValue detail = json(object(field("action", action)));
437            request(CREST_PROTOCOL, operation, detail);
438        } else {
439            request(CREST_PROTOCOL, operation);
440        }
441        return self();
442    }
443
444    /**
445     * Sets common fields from services contexts.
446     *
447     * @param context The services context.
448     *
449     * @see #transactionIdFromContext(Context)
450     * @see #clientFromContext(Context)
451     * @see #serverFromContext(Context)
452     * @see #httpFromContext(Context)
453     *
454     * @return this builder
455     */
456    public final T forContext(Context context) {
457        transactionIdFromContext(context);
458        clientFromContext(context);
459        serverFromContext(context);
460        httpFromContext(context);
461        return self();
462    }
463
464    /**
465     * Sets common fields from CREST contexts and request.
466     *
467     * @param context The CREST context.
468     * @param request The CREST request.
469     *
470     * @see #transactionIdFromContext(Context)
471     * @see #clientFromContext(Context)
472     * @see #serverFromContext(Context)
473     * @see #httpFromContext(Context)
474     * @see #requestFromCrestRequest(Request)
475     *
476     * @return this builder
477     */
478    public final T forHttpRequest(Context context, Request request) {
479        forContext(context);
480        requestFromCrestRequest(request);
481        return self();
482    }
483
484    /**
485     * The status of the access request.
486     */
487    public enum ResponseStatus {
488        /** The access request was successfully completed. */
489        SUCCESSFUL,
490        /** The access request was not successfully completed. */
491        FAILED
492    }
493}