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}