All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.helidon.security.spi.AuditProvider Maven / Gradle / Ivy

There is a newer version: 4.1.1
Show newest version
/*
 * Copyright (c) 2018, 2021 Oracle and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.helidon.security.spi;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import io.helidon.security.AuditEvent;

/**
 * Audit provider, storing audit events.
 * If no custom audit provider is defined (using
 * {@link io.helidon.security.Security.Builder#addAuditProvider(AuditProvider)}) a default provider will be used.
 * 

* Default audit provider logs most events in {@link java.util.logging.Level#FINEST}. * {@link AuditEvent.AuditSeverity#AUDIT_FAILURE} * and {@link AuditEvent.AuditSeverity#ERROR} * are logged in {@link java.util.logging.Level#SEVERE} and {@link AuditEvent.AuditSeverity#WARN} is logged in {@link * java.util.logging.Level#WARNING} level. * *

* Format of default audit provider log record (all end of lines are removed from message, not from stack trace): * {@code * year.month.day hour(24):minute:second LogLevel AUDIT auditSeverity tracingId auditEventType auditEventClassName location(class) * location(method) location(sourceFile) location(line) :: "audit message" * } */ @FunctionalInterface public interface AuditProvider extends SecurityProvider { /** * Return your subscriber for audit events. The method is invoked synchronously, so if you want to have low impact on * performance, you should handle possible asynchronous processing in the provider implementation. * * @return Consumer that will receive all audit events of this security realm */ Consumer auditConsumer(); /** * Audit event sent to Audit provider. Wraps tracing id and AuditEvent sent by * a component/user. */ interface TracedAuditEvent extends AuditEvent { /** * Tracing id of the current audit event, generated by SecurityContext. * * @return String with tracing id */ String tracingId(); /** * Source of this audit event (class, method, line number etc.). * * @return Source of the audit event */ AuditSource auditSource(); /** * Creates a formatted message from this events message format and parameters. * * @return formatted message */ default String formatMessage() { List params = params(); List msgParams = new ArrayList<>(params.size()); params.forEach(auditParam -> msgParams.add(auditParam.value().orElse(""))); return String.format(messageFormat(), msgParams.toArray(new Object[0])); } } /** * Source of an audit source (as in "where this audit event originated"). */ interface AuditSource { /** * Build an audit source. * * @return AuditSource for current stack trace */ static AuditSource create() { StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); //I want to filter out all methods in the root of security package //including security builder Optional frame = walker .walk(stream -> stream .filter(f -> !f.getClassName().startsWith("sun.")) .filter(f -> !f.getClassName().startsWith("java.")) .filter(f -> { // if this is a unit test class, return it if (f.getClassName().endsWith("Test")) { return true; } // filter out security classes return !isSecurityClass(f); }) .findFirst()); if (!frame.isPresent()) { frame = walker .walk(stream -> { List elems = stream .filter(f -> !f.getClassName().startsWith("sun.")) .filter(f -> !f.getClassName().startsWith("java.")) .collect(Collectors.toList()); if (elems.isEmpty()) { return Optional.empty(); } return Optional.of(elems.get(elems.size() - 1)); } ); } if (frame.isPresent()) { StackWalker.StackFrame stackFrame = frame.get(); return new AuditSource() { @Override public Optional className() { return Optional.of(stackFrame.getClassName()); } @Override public Optional methodName() { return Optional.of(stackFrame.getMethodName()); } @Override public Optional fileName() { return Optional.ofNullable(stackFrame.getFileName()); } @Override public OptionalInt lineNumber() { return (stackFrame.getLineNumber() < 0) ? OptionalInt.empty() : OptionalInt.of(stackFrame.getLineNumber()); } }; } else { // class name, method name, source file, line number return new AuditSource() { }; } } /** * Check if the stack element is an actual Helidon Security class. * * @param element element to check * @return {@code true} if the class comes from Helidon Security */ static boolean isSecurityClass(StackWalker.StackFrame element) { String className = element.getClassName(); int last = className.lastIndexOf('.'); String packageName = (last > 0) ? className.substring(0, last) : ""; if (packageName.equals(AuditEvent.class.getPackage().getName())) { return true; } return packageName.equals(AuditProvider.class.getPackage().getName()); } /** * Name of the class that caused this event. * @return class name or empty if not provided */ default Optional className() { return Optional.empty(); } /** * Method name that caused this event. * @return method name or empty if not provided */ default Optional methodName() { return Optional.empty(); } /** * File name of the source that caused this event. * @return file name or empty if not provided */ default Optional fileName() { return Optional.empty(); } /** * Line number within the source file that caused this event. * @return line number or empty if not provided */ default OptionalInt lineNumber() { return OptionalInt.empty(); } } }