Repository: syncope Updated Branches: refs/heads/2_0_X bfd47df70 -> dc4b83ef1
[SYNCOPE-1144] configurable audit appenders with message rewrite option Project: http://git-wip-us.apache.org/repos/asf/syncope/repo Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/dc4b83ef Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/dc4b83ef Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/dc4b83ef Branch: refs/heads/2_0_X Commit: dc4b83ef1cbf76240bf24c9b8a7e8cc9ae0a9994 Parents: bfd47df Author: Andrea Patricelli <andreapatrice...@apache.org> Authored: Tue Jul 4 12:09:45 2017 +0200 Committer: Andrea Patricelli <andreapatrice...@apache.org> Committed: Thu Jul 13 14:21:14 2017 +0200 ---------------------------------------------------------------------- .../core/logic/AbstractAuditAppender.java | 71 +++++++++++++++ .../syncope/core/logic/AuditAppender.java | 51 +++++++++++ .../core/logic/DefaultAuditAppender.java | 54 ++++++++++++ .../core/logic/DefaultRewriteAuditAppender.java | 64 ++++++++++++++ .../apache/syncope/core/logic/LoggerLogic.java | 46 +++++++++- .../core/logic/PassThroughRewritePolicy.java | 41 +++++++++ .../init/ClassPathScanImplementationLookup.java | 15 ++++ .../syncope/core/logic/init/LoggerAccessor.java | 12 ++- .../syncope/core/logic/init/LoggerLoader.java | 79 +++++++++++++++++ .../persistence/api/ImplementationLookup.java | 5 +- .../jpa/DummyImplementationLookup.java | 5 ++ .../provisioning/java/AuditManagerImpl.java | 13 ++- .../AbstractPropagationTaskExecutor.java | 2 +- .../java/DummyImplementationLookup.java | 5 ++ .../core/reference/ITImplementationLookup.java | 13 +++ .../reference/SyslogRewriteAuditAppender.java | 80 +++++++++++++++++ .../core/reference/TestFileAuditAppender.java | 86 ++++++++++++++++++ .../reference/TestFileRewriteAuditAppender.java | 84 ++++++++++++++++++ .../fit/core/reference/TestRewritePolicy.java | 46 ++++++++++ .../apache/syncope/fit/core/LoggerITCase.java | 93 ++++++++++++++++++++ .../src/test/resources/core-test.properties | 1 + .../reference-guide/concepts/audit.adoc | 50 ++++++++++- .../workingwithapachesyncope/customization.adoc | 6 ++ 23 files changed, 910 insertions(+), 12 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAuditAppender.java ---------------------------------------------------------------------- diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAuditAppender.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAuditAppender.java new file mode 100644 index 0000000..5383a81 --- /dev/null +++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAuditAppender.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.syncope.core.logic; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.appender.rewrite.RewriteAppender; +import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; + +public abstract class AbstractAuditAppender implements AuditAppender { + + protected String domainName; + + protected Appender targetAppender; + + protected RewriteAppender rewriteAppender; + + @Override + public abstract void init(); + + public abstract void initTargetAppender(); + + public abstract void initRewriteAppender(); + + @Override + public abstract RewritePolicy getRewritePolicy(); + + @Override + public String getDomainName() { + return domainName; + } + + @Override + public void setDomainName(final String domainName) { + this.domainName = domainName; + } + + @Override + public abstract String getTargetAppenderName(); + + @Override + public boolean isRewriteEnabled() { + return rewriteAppender != null; + } + + @Override + public RewriteAppender getRewriteAppender() { + return rewriteAppender; + } + + @Override + public Appender getTargetAppender() { + return targetAppender; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/AuditAppender.java ---------------------------------------------------------------------- diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AuditAppender.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AuditAppender.java new file mode 100644 index 0000000..93dbfed --- /dev/null +++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AuditAppender.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.syncope.core.logic; + +import java.util.Set; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.appender.rewrite.RewriteAppender; +import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; +import org.apache.syncope.common.lib.types.AuditLoggerName; + +/** + * Basic interface to implement to define a custom audit appender + * + * @see org.apache.syncope.core.logic.DefaultRewriteAuditAppender or org.apache.syncope.core.logic.DefaultAuditAppender + */ +public interface AuditAppender { + + void init(); + + Set<AuditLoggerName> getEvents(); + + Appender getTargetAppender(); + + RewritePolicy getRewritePolicy(); + + String getTargetAppenderName(); + + void setDomainName(String name); + + String getDomainName(); + + boolean isRewriteEnabled(); + + RewriteAppender getRewriteAppender(); +} http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultAuditAppender.java ---------------------------------------------------------------------- diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultAuditAppender.java b/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultAuditAppender.java new file mode 100644 index 0000000..1fd9e5e --- /dev/null +++ b/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultAuditAppender.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.syncope.core.logic; + +import java.util.Collections; +import java.util.Set; +import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; +import org.apache.syncope.common.lib.types.AuditLoggerName; + +/** + * Default (abstract) implementation of custom audit appender. + * It is bound to an empty collection of events, i.e. it does not create any logger. + * This class has to be extended by non-rewrite appenders + * + * @see org.apache.syncope.fit.core.reference.TestFileAuditAppender + */ +public abstract class DefaultAuditAppender extends AbstractAuditAppender { + + @Override + public void init() { + initTargetAppender(); + } + + @Override + public Set<AuditLoggerName> getEvents() { + return Collections.emptySet(); + } + + @Override + public void initRewriteAppender() { + } + + @Override + public RewritePolicy getRewritePolicy() { + return null; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultRewriteAuditAppender.java ---------------------------------------------------------------------- diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultRewriteAuditAppender.java b/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultRewriteAuditAppender.java new file mode 100644 index 0000000..207502e --- /dev/null +++ b/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultRewriteAuditAppender.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.syncope.core.logic; + +import java.util.Collections; +import java.util.Set; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.rewrite.RewriteAppender; +import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; +import org.apache.logging.log4j.core.config.AppenderRef; +import org.apache.syncope.common.lib.types.AuditLoggerName; + +/** + * Default (abstract) implementation of custom rewriting audit appender; it provides rewrite appender definition and + * a default "pass-through" policy. It is bound to an empty collection of events, i.e. it does not create any logger. + * This class has to be extended by rewrite appenders. + * + * @see org.apache.syncope.fit.core.reference.TestFileRewriteAuditAppender + */ +public abstract class DefaultRewriteAuditAppender extends AbstractAuditAppender { + + @Override + public void init() { + initTargetAppender(); + initRewriteAppender(); + } + + @Override + public void initRewriteAppender() { + rewriteAppender = RewriteAppender.createAppender(getTargetAppenderName() + "_rewrite", + "true", + new AppenderRef[] { AppenderRef.createAppenderRef(getTargetAppenderName(), Level.DEBUG, null) }, + ((LoggerContext) LogManager.getContext(false)).getConfiguration(), getRewritePolicy(), null); + } + + @Override + public Set<AuditLoggerName> getEvents() { + return Collections.emptySet(); + } + + @Override + public RewritePolicy getRewritePolicy() { + return PassThroughRewritePolicy.createPolicy(); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java ---------------------------------------------------------------------- diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java index 5315ea9..667efc8 100644 --- a/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java +++ b/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java @@ -25,7 +25,9 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.collections4.Predicate; import org.apache.commons.collections4.PredicateUtils; import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.TransformerUtils; @@ -60,9 +62,11 @@ import org.apache.syncope.core.persistence.api.entity.EntityFactory; import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource; import org.apache.syncope.core.persistence.api.entity.Logger; import org.apache.syncope.core.persistence.api.entity.task.SchedTask; +import org.apache.syncope.core.provisioning.java.AuditManagerImpl; import org.apache.syncope.core.spring.BeanUtils; import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate; import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate; +import org.apache.syncope.core.spring.security.AuthContextUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; @@ -216,10 +220,41 @@ public class LoggerLogic extends AbstractTransactionalLogic<LoggerTO> { syncopeLogger.setLevel(LoggerLevel.fromLevel(level)); syncopeLogger = loggerDAO.save(syncopeLogger); + boolean isAudit = LoggerType.AUDIT.equals(syncopeLogger.getType()); LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + String domainAuditLoggerName = + AuditManagerImpl.getDomainAuditEventLoggerName(AuthContextUtils.getDomain(), syncopeLogger. + getKey()); LoggerConfig logConf = SyncopeConstants.ROOT_LOGGER.equals(name) ? ctx.getConfiguration().getLoggerConfig(LogManager.ROOT_LOGGER_NAME) - : ctx.getConfiguration().getLoggerConfig(name); + : isAudit + ? ctx.getConfiguration().getLoggerConfig(domainAuditLoggerName) + : ctx.getConfiguration().getLoggerConfig(name); + + if (isAudit) { + // SYNCOPE-1144 For each custom audit appender class add related appenders to log4j logger + List<AuditAppender> auditAppenders = loggerLoader.auditAppenders(AuthContextUtils.getDomain()); + boolean isRootLogConf = LogManager.ROOT_LOGGER_NAME.equals(logConf.getName()); + final String loggerKey = syncopeLogger.getKey(); + if (isRootLogConf) { + logConf = new LoggerConfig(domainAuditLoggerName, null, false); + } + for (AuditAppender auditAppender : auditAppenders) { + + if (IterableUtils.matchesAny(auditAppender.getEvents(), new Predicate<AuditLoggerName>() { + + @Override + public boolean evaluate(final AuditLoggerName auditLoggerName) { + return loggerKey.equalsIgnoreCase(auditLoggerName.toLoggerName()); + } + })) { + loggerLoader.addAppenderToContext(ctx, auditAppender, logConf); + } + } + if (isRootLogConf) { + ctx.getConfiguration().addLogger(domainAuditLoggerName, logConf); + } + } logConf.setLevel(level); ctx.updateLoggers(); @@ -254,6 +289,7 @@ public class LoggerLogic extends AbstractTransactionalLogic<LoggerTO> { if (expectedType != syncopeLogger.getType()) { throwInvalidLogger(expectedType); } + boolean isAudit = LoggerType.AUDIT.equals(syncopeLogger.getType()); LoggerTO loggerToDelete = new LoggerTO(); BeanUtils.copyProperties(syncopeLogger, loggerToDelete); @@ -263,8 +299,14 @@ public class LoggerLogic extends AbstractTransactionalLogic<LoggerTO> { // set log level to OFF in order to disable configured logger until next reboot LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + String domainAuditLoggerName = + AuditManagerImpl.getDomainAuditEventLoggerName(AuthContextUtils.getDomain(), syncopeLogger. + getKey()); org.apache.logging.log4j.core.Logger logger = SyncopeConstants.ROOT_LOGGER.equals(name) - ? ctx.getLogger(LogManager.ROOT_LOGGER_NAME) : ctx.getLogger(name); + ? ctx.getLogger(LogManager.ROOT_LOGGER_NAME) + : isAudit + ? ctx.getLogger(domainAuditLoggerName) + : ctx.getLogger(name); logger.setLevel(Level.OFF); ctx.updateLoggers(); http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/PassThroughRewritePolicy.java ---------------------------------------------------------------------- diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/PassThroughRewritePolicy.java b/core/logic/src/main/java/org/apache/syncope/core/logic/PassThroughRewritePolicy.java new file mode 100644 index 0000000..08c6c4e --- /dev/null +++ b/core/logic/src/main/java/org/apache/syncope/core/logic/PassThroughRewritePolicy.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.syncope.core.logic; + +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +@Plugin(name = "PassThroughRewritePolicy", category = Core.CATEGORY_NAME, elementType = "rewritePolicy", + printObject = true) +public class PassThroughRewritePolicy implements RewritePolicy { + + @Override + public LogEvent rewrite(final LogEvent event) { + return event; + } + + @PluginFactory + public static PassThroughRewritePolicy createPolicy() { + return new PassThroughRewritePolicy(); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java ---------------------------------------------------------------------- diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java index fd2f1fb..902fa4d 100644 --- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java +++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java @@ -28,6 +28,7 @@ import java.util.Set; import org.apache.syncope.common.lib.policy.AccountRuleConf; import org.apache.syncope.common.lib.policy.PasswordRuleConf; import org.apache.syncope.common.lib.report.ReportletConf; +import org.apache.syncope.core.logic.AuditAppender; import org.apache.syncope.core.persistence.api.ImplementationLookup; import org.apache.syncope.core.persistence.api.attrvalue.validation.Validator; import org.apache.syncope.core.persistence.api.dao.AccountRule; @@ -77,6 +78,8 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup { private Map<Class<? extends PasswordRuleConf>, Class<? extends PasswordRule>> passwordRuleClasses; + private Set<Class<?>> auditAppenderClasses; + @Override public Integer getPriority() { return 400; @@ -103,6 +106,7 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup { reportletClasses = new HashMap<>(); accountRuleClasses = new HashMap<>(); passwordRuleClasses = new HashMap<>(); + auditAppenderClasses = new HashSet<>(); ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); scanner.addIncludeFilter(new AssignableTypeFilter(JWTSSOProvider.class)); @@ -119,6 +123,7 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup { scanner.addIncludeFilter(new AssignableTypeFilter(PullCorrelationRule.class)); scanner.addIncludeFilter(new AssignableTypeFilter(Validator.class)); scanner.addIncludeFilter(new AssignableTypeFilter(NotificationRecipientsProvider.class)); + scanner.addIncludeFilter(new AssignableTypeFilter(AuditAppender.class)); for (BeanDefinition bd : scanner.findCandidateComponents(getBasePackage())) { try { @@ -207,6 +212,11 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup { if (NotificationRecipientsProvider.class.isAssignableFrom(clazz) && !isAbstractClazz) { classNames.get(Type.NOTIFICATION_RECIPIENTS_PROVIDER).add(bd.getBeanClassName()); } + + if (AuditAppender.class.isAssignableFrom(clazz) && !isAbstractClazz) { + classNames.get(Type.AUDIT_APPENDER).add(clazz.getName()); + auditAppenderClasses.add(clazz); + } } catch (Throwable t) { LOG.warn("Could not inspect class {}", bd.getBeanClassName(), t); } @@ -249,4 +259,9 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup { return passwordRuleClasses.get(passwordRuleConfClass); } + + @Override + public Set<Class<?>> getAuditAppenderClasses() { + return auditAppenderClasses; + } } http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerAccessor.java ---------------------------------------------------------------------- diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerAccessor.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerAccessor.java index 47fa990..675fd94 100644 --- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerAccessor.java +++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerAccessor.java @@ -30,6 +30,7 @@ import org.apache.syncope.core.spring.security.AuthContextUtils; import org.apache.syncope.core.persistence.api.dao.LoggerDAO; import org.apache.syncope.core.persistence.api.entity.EntityFactory; import org.apache.syncope.core.persistence.api.entity.Logger; +import org.apache.syncope.core.provisioning.java.AuditManagerImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -57,7 +58,8 @@ public class LoggerAccessor { } } for (Logger syncopeLogger : loggerDAO.findAll(LoggerType.AUDIT)) { - syncopeLoggers.put(syncopeLogger.getKey(), syncopeLogger); + syncopeLoggers.put(AuditManagerImpl.getDomainAuditEventLoggerName(AuthContextUtils.getDomain(), + syncopeLogger.getKey()), syncopeLogger); } /* @@ -71,7 +73,8 @@ public class LoggerAccessor { if (syncopeLoggers.containsKey(loggerName)) { logConf.setLevel(syncopeLoggers.get(loggerName).getLevel().getLevel()); syncopeLoggers.remove(loggerName); - } else if (!loggerName.equals(LoggerType.AUDIT.getPrefix())) { + } else if (!loggerName.startsWith(LoggerType.AUDIT.getPrefix()) || !loggerName.startsWith( + AuthContextUtils.getDomain() + "." + LoggerType.AUDIT.getPrefix())) { Logger syncopeLogger = entityFactory.newEntity(Logger.class); syncopeLogger.setKey(loggerName); syncopeLogger.setLevel(LoggerLevel.fromLevel(logConf.getLevel())); @@ -84,8 +87,9 @@ public class LoggerAccessor { /* * Foreach SyncopeLogger not found in log4j create a new log4j logger with given name and level. */ - for (Logger syncopeLogger : syncopeLoggers.values()) { - LoggerConfig logConf = ctx.getConfiguration().getLoggerConfig(syncopeLogger.getKey()); + for (Map.Entry<String, Logger> entry : syncopeLoggers.entrySet()) { + Logger syncopeLogger = entry.getValue(); + LoggerConfig logConf = ctx.getConfiguration().getLoggerConfig(entry.getKey()); logConf.setLevel(syncopeLogger.getLevel().getLevel()); } http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java ---------------------------------------------------------------------- diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java index 60d02eb..4a24d32 100644 --- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java +++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java @@ -20,7 +20,9 @@ package org.apache.syncope.core.logic.init; import java.sql.Connection; import java.sql.SQLException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.sql.DataSource; import org.apache.logging.log4j.Level; @@ -31,13 +33,20 @@ import org.apache.logging.log4j.core.appender.db.ColumnMapping; import org.apache.logging.log4j.core.appender.db.jdbc.ColumnConfig; import org.apache.logging.log4j.core.appender.db.jdbc.ConnectionSource; import org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppender; +import org.apache.logging.log4j.core.appender.rewrite.RewriteAppender; import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.syncope.common.lib.types.AuditLoggerName; +import org.apache.syncope.core.logic.AuditAppender; import org.apache.syncope.core.logic.MemoryAppender; import org.apache.syncope.core.provisioning.java.AuditManagerImpl; import org.apache.syncope.core.spring.security.AuthContextUtils; import org.apache.syncope.core.persistence.api.DomainsHolder; +import org.apache.syncope.core.persistence.api.ImplementationLookup; import org.apache.syncope.core.persistence.api.SyncopeLoader; +import org.apache.syncope.core.spring.ApplicationContextProvider; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.stereotype.Component; @@ -50,6 +59,9 @@ public class LoggerLoader implements SyncopeLoader { @Autowired private LoggerAccessor loggerAccessor; + @Autowired + private ImplementationLookup implementationLookup; + private final Map<String, MemoryAppender> memoryAppenders = new HashMap<>(); @Override @@ -81,6 +93,7 @@ public class LoggerLoader implements SyncopeLoader { setConfiguration(ctx.getConfiguration()).setName("THROWABLE").setPattern("%ex{full}").build() }; ColumnMapping[] columnMappings = new ColumnMapping[0]; + for (Map.Entry<String, DataSource> entry : domainsHolder.getDomains().entrySet()) { Appender appender = ctx.getConfiguration().getAppender("audit_for_" + entry.getKey()); if (appender == null) { @@ -103,6 +116,9 @@ public class LoggerLoader implements SyncopeLoader { logConf.setLevel(Level.DEBUG); ctx.getConfiguration().addLogger(AuditManagerImpl.getDomainAuditLoggerName(entry.getKey()), logConf); + // SYNCOPE-1144 For each custom audit appender class add related appenders to log4j logger + configureCustomAppenders(entry.getKey(), ctx); + AuthContextUtils.execWithAuthContext(entry.getKey(), new AuthContextUtils.Executable<Void>() { @Override @@ -120,6 +136,69 @@ public class LoggerLoader implements SyncopeLoader { return memoryAppenders; } + public void configureCustomAppenders(final String domainName, final LoggerContext ctx) { + List<AuditAppender> auditAppenders = auditAppenders(domainName); + for (AuditAppender auditAppender : auditAppenders) { + for (AuditLoggerName event : auditAppender.getEvents()) { + String domainAuditLoggerName = + AuditManagerImpl.getDomainAuditEventLoggerName(domainName, event.toLoggerName()); + LoggerConfig eventLogConf = ctx.getConfiguration().getLoggerConfig(domainAuditLoggerName); + boolean isRootLogConf = LogManager.ROOT_LOGGER_NAME.equals(eventLogConf.getName()); + + if (isRootLogConf) { + eventLogConf = new LoggerConfig(domainAuditLoggerName, null, false); + } + addAppenderToContext(ctx, auditAppender, eventLogConf); + eventLogConf.setLevel(Level.DEBUG); + if (isRootLogConf) { + ctx.getConfiguration().addLogger(domainAuditLoggerName, eventLogConf); + } + } + } + } + + public List<AuditAppender> auditAppenders(final String domainName) throws BeansException { + List<AuditAppender> auditAppenders = new ArrayList<>(); + for (Class<?> clazz : implementationLookup.getAuditAppenderClasses()) { + AuditAppender auditAppender; + if (ApplicationContextProvider.getBeanFactory().containsSingleton(clazz.getName())) { + auditAppender = (AuditAppender) ApplicationContextProvider.getBeanFactory(). + getSingleton(clazz.getName()); + } else { + auditAppender = (AuditAppender) ApplicationContextProvider.getBeanFactory(). + createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true); + auditAppender.setDomainName(domainName); + auditAppender.init(); + } + auditAppenders.add(auditAppender); + } + return auditAppenders; + } + + public void addAppenderToContext( + final LoggerContext ctx, + final AuditAppender auditAppender, + final LoggerConfig eventLogConf) { + Appender targetAppender = ctx.getConfiguration().getAppender(auditAppender.getTargetAppenderName()); + if (targetAppender == null) { + targetAppender = auditAppender.getTargetAppender(); + } + targetAppender.start(); + ctx.getConfiguration().addAppender(targetAppender); + if (auditAppender.isRewriteEnabled()) { + RewriteAppender rewriteAppender = ctx.getConfiguration().getAppender(auditAppender. + getTargetAppenderName() + "_rewrite"); + if (rewriteAppender == null) { + rewriteAppender = auditAppender.getRewriteAppender(); + } + rewriteAppender.start(); + ctx.getConfiguration().addAppender(rewriteAppender); + eventLogConf.addAppender(rewriteAppender, Level.DEBUG, null); + } else { + eventLogConf.addAppender(targetAppender, Level.DEBUG, null); + } + } + private static class DataSourceConnectionSource implements ConnectionSource { private final DataSource dataSource; http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java ---------------------------------------------------------------------- diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java index 2d3438f..c510677 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java @@ -43,7 +43,8 @@ public interface ImplementationLookup extends SyncopeLoader { PUSH_ACTIONS, PULL_CORRELATION_RULE, VALIDATOR, - NOTIFICATION_RECIPIENTS_PROVIDER; + NOTIFICATION_RECIPIENTS_PROVIDER, + AUDIT_APPENDER; } @@ -56,4 +57,6 @@ public interface ImplementationLookup extends SyncopeLoader { Class<? extends AccountRule> getAccountRuleClass(Class<? extends AccountRuleConf> accountRuleConfClass); Class<? extends PasswordRule> getPasswordRuleClass(Class<? extends PasswordRuleConf> passwordRuleConfClass); + + Set<Class<?>> getAuditAppenderClasses(); } http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java ---------------------------------------------------------------------- diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java index 4a785ff..1875fb1 100644 --- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java @@ -75,4 +75,9 @@ public class DummyImplementationLookup implements ImplementationLookup { return DefaultPasswordRule.class; } + @Override + public Set<Class<?>> getAuditAppenderClasses() { + return Collections.emptySet(); + } + } http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/AuditManagerImpl.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/AuditManagerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/AuditManagerImpl.java index 14180ec..760a9fa 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/AuditManagerImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/AuditManagerImpl.java @@ -45,6 +45,10 @@ public class AuditManagerImpl implements AuditManager { return LoggerType.AUDIT.getPrefix() + "." + domain; } + public static String getDomainAuditEventLoggerName(final String domain, final String loggerName) { + return domain + "." + loggerName; + } + @Override public boolean auditRequested( final AuditElements.EventCategoryType type, @@ -118,10 +122,15 @@ public class AuditManagerImpl implements AuditManager { loggerDAO.find(auditEntry.getLogger().toLoggerName()); if (syncopeLogger != null && syncopeLogger.getLevel() == LoggerLevel.DEBUG) { Logger logger = LoggerFactory.getLogger(getDomainAuditLoggerName(AuthContextUtils.getDomain())); + Logger eventLogger = LoggerFactory.getLogger(getDomainAuditEventLoggerName(AuthContextUtils.getDomain(), + syncopeLogger.getKey())); + String serializedAuditEntry = POJOHelper.serialize(auditEntry); if (throwable == null) { - logger.debug(POJOHelper.serialize(auditEntry)); + logger.debug(serializedAuditEntry); + eventLogger.debug(POJOHelper.serialize(auditEntry)); } else { - logger.debug(POJOHelper.serialize(auditEntry), throwable); + logger.debug(serializedAuditEntry, throwable); + eventLogger.debug(serializedAuditEntry, throwable); } } } http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java index ff91c41..a14ba2f 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java @@ -508,7 +508,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask for (PropagationActions action : actions) { action.after(task, execution, afterObj); } - + // SYNCOPE-1136 String anyTypeKind = task.getAnyTypeKind() == null ? "realm" : task.getAnyTypeKind().name().toLowerCase(); String operation = task.getOperation().name().toLowerCase(); boolean notificationsAvailable = notificationManager.notificationsAvailable( http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java index 011364c..cac936d 100644 --- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java +++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java @@ -75,4 +75,9 @@ public class DummyImplementationLookup implements ImplementationLookup { return DefaultPasswordRule.class; } + @Override + public Set<Class<?>> getAuditAppenderClasses() { + return Collections.emptySet(); + } + } http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java ---------------------------------------------------------------------- diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java index 3ea2715..81f94b9 100644 --- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java +++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java @@ -146,6 +146,11 @@ public class ITImplementationLookup implements ImplementationLookup { classNames = new HashSet<>(); classNames.add(TestNotificationRecipientsProvider.class.getName()); put(Type.NOTIFICATION_RECIPIENTS_PROVIDER, classNames); + + classNames = new HashSet<>(); + classNames.add(TestFileRewriteAuditAppender.class.getName()); + classNames.add(TestFileAuditAppender.class.getName()); + put(Type.AUDIT_APPENDER, classNames); } }; @@ -256,4 +261,12 @@ public class ITImplementationLookup implements ImplementationLookup { return PASSWORD_RULE_CLASSES.get(passwordRuleConfClass); } + + @Override + public Set<Class<?>> getAuditAppenderClasses() { + Set<Class<?>> classes = new HashSet<>(); + classes.add(TestFileRewriteAuditAppender.class); + classes.add(TestFileAuditAppender.class); + return classes; + } } http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/SyslogRewriteAuditAppender.java ---------------------------------------------------------------------- diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/SyslogRewriteAuditAppender.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/SyslogRewriteAuditAppender.java new file mode 100644 index 0000000..9a192df --- /dev/null +++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/SyslogRewriteAuditAppender.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.syncope.fit.core.reference; + +import java.util.HashSet; +import java.util.Set; +import org.apache.logging.log4j.core.appender.SyslogAppender; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.net.Facility; +import org.apache.logging.log4j.core.net.Protocol; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.AuditLoggerName; +import org.apache.syncope.core.logic.ConnectorLogic; +import org.apache.syncope.core.logic.DefaultRewriteAuditAppender; +import org.apache.syncope.core.logic.ResourceLogic; + +public class SyslogRewriteAuditAppender extends DefaultRewriteAuditAppender { + + @Override + public Set<AuditLoggerName> getEvents() { + Set<AuditLoggerName> events = new HashSet<>(); + events.add( + new AuditLoggerName( + AuditElements.EventCategoryType.LOGIC, + ResourceLogic.class.getSimpleName(), + null, + "update", + AuditElements.Result.SUCCESS)); + events.add( + new AuditLoggerName( + AuditElements.EventCategoryType.LOGIC, + ConnectorLogic.class.getSimpleName(), + null, + "update", + AuditElements.Result.SUCCESS)); + events.add( + new AuditLoggerName( + AuditElements.EventCategoryType.LOGIC, + ResourceLogic.class.getSimpleName(), + null, + "delete", + AuditElements.Result.SUCCESS)); + return events; + } + + @Override + public void initTargetAppender() { + targetAppender = SyslogAppender.newSyslogAppenderBuilder() + .withName(getTargetAppenderName()) + .withHost("localhost") + .withPort(514) + .withProtocol(Protocol.UDP) + .withLayout( + PatternLayout.newBuilder().withPattern("%d{ISO8601} %-5level %logger - %msg%n").build()) + .setFacility(Facility.LOCAL1) + .build(); + } + + @Override + public String getTargetAppenderName() { + return "audit_for_" + domainName + "_syslog"; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java ---------------------------------------------------------------------- diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java new file mode 100644 index 0000000..cffc8f7 --- /dev/null +++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.syncope.fit.core.reference; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.AuditLoggerName; +import org.apache.syncope.core.logic.ConnectorLogic; +import org.apache.syncope.core.logic.DefaultAuditAppender; +import org.apache.syncope.core.logic.ResourceLogic; + +public class TestFileAuditAppender extends DefaultAuditAppender { + + @Override + public Set<AuditLoggerName> getEvents() { + Set<AuditLoggerName> events = new HashSet<>(); + events.add( + new AuditLoggerName( + AuditElements.EventCategoryType.LOGIC, + ResourceLogic.class.getSimpleName(), + null, + "create", + AuditElements.Result.SUCCESS)); + events.add( + new AuditLoggerName( + AuditElements.EventCategoryType.LOGIC, + ConnectorLogic.class.getSimpleName(), + null, + "update", + AuditElements.Result.SUCCESS)); + return events; + } + + @Override + public void initTargetAppender() { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + // get log file path from existing file appender + RollingRandomAccessFileAppender mainFile = + (RollingRandomAccessFileAppender) ctx.getConfiguration().getAppender("mainFile"); + + String pathPrefix = mainFile == null + ? System.getProperty("user.dir") + StringUtils.replace("/target/log", "/", File.separator) + + File.separator + : StringUtils.replace(mainFile.getFileName(), "core.log", StringUtils.EMPTY); + + targetAppender = FileAppender.newBuilder() + .withName(getTargetAppenderName()) + .withAppend(true) + .withFileName(pathPrefix + getTargetAppenderName() + ".log") + .withLayout( + PatternLayout.newBuilder() + .withPattern("%d{HH:mm:ss.SSS} %-5level %logger - %msg%n") + .build()). + build(); + } + + @Override + public String getTargetAppenderName() { + return "audit_for_" + domainName + "_norewrite_file"; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java ---------------------------------------------------------------------- diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java new file mode 100644 index 0000000..932ce56 --- /dev/null +++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.syncope.fit.core.reference; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender; +import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.AuditLoggerName; +import org.apache.syncope.core.logic.DefaultRewriteAuditAppender; +import org.apache.syncope.core.logic.ResourceLogic; + +public class TestFileRewriteAuditAppender extends DefaultRewriteAuditAppender { + + @Override + public Set<AuditLoggerName> getEvents() { + Set<AuditLoggerName> events = new HashSet<>(); + events.add( + new AuditLoggerName( + AuditElements.EventCategoryType.LOGIC, + ResourceLogic.class.getSimpleName(), + null, + "update", + AuditElements.Result.SUCCESS)); + return events; + } + + @Override + public void initTargetAppender() { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + // get log file path from existing file appender + RollingRandomAccessFileAppender mainFile = + (RollingRandomAccessFileAppender) ctx.getConfiguration().getAppender("mainFile"); + + String pathPrefix = mainFile == null + ? System.getProperty("user.dir") + StringUtils.replace("/target/log", "/", File.separator) + + File.separator + : StringUtils.replace(mainFile.getFileName(), "core.log", StringUtils.EMPTY); + + targetAppender = FileAppender.newBuilder() + .withName(getTargetAppenderName()) + .withAppend(true) + .withFileName(pathPrefix + getTargetAppenderName() + ".log") + .withLayout( + PatternLayout.newBuilder() + .withPattern("%d{HH:mm:ss.SSS} %-5level %logger - %msg%n") + .build()) + .build(); + } + + @Override + public String getTargetAppenderName() { + return "audit_for_" + domainName + "_file"; + } + + @Override + public RewritePolicy getRewritePolicy() { + return TestRewritePolicy.createPolicy(); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestRewritePolicy.java ---------------------------------------------------------------------- diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestRewritePolicy.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestRewritePolicy.java new file mode 100644 index 0000000..641f203 --- /dev/null +++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestRewritePolicy.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.syncope.fit.core.reference; + +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.status.StatusLogger; + +@Plugin(name = "TestRewritePolicy", category = Core.CATEGORY_NAME, elementType = "rewritePolicy", + printObject = true) +public class TestRewritePolicy implements RewritePolicy { + + protected static final StatusLogger LOGGER = StatusLogger.getLogger(); + + @Override + public LogEvent rewrite(final LogEvent event) { + return new Log4jLogEvent.Builder(event).setMessage(new SimpleMessage("This is a static test message")).build(); + } + + @PluginFactory + public static TestRewritePolicy createPolicy() { + return new TestRewritePolicy(); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LoggerITCase.java ---------------------------------------------------------------------- diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LoggerITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LoggerITCase.java index 5720785..9bc6ed6 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LoggerITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LoggerITCase.java @@ -24,17 +24,27 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.text.ParseException; import java.util.List; +import java.util.Properties; import javax.ws.rs.core.Response; import javax.xml.ws.WebServiceException; import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.Predicate; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.log.EventCategoryTO; import org.apache.syncope.common.lib.log.LogAppender; import org.apache.syncope.common.lib.log.LogStatementTO; import org.apache.syncope.common.lib.log.LoggerTO; +import org.apache.syncope.common.lib.to.ConnInstanceTO; +import org.apache.syncope.common.lib.to.ConnPoolConfTO; +import org.apache.syncope.common.lib.to.ResourceTO; import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.AuditElements; import org.apache.syncope.common.lib.types.AuditElements.EventCategoryType; @@ -43,11 +53,13 @@ import org.apache.syncope.common.lib.types.LoggerLevel; import org.apache.syncope.common.lib.types.LoggerType; import org.apache.syncope.common.lib.types.ResourceOperation; import org.apache.syncope.common.rest.api.LoggerWrapper; +import org.apache.syncope.core.logic.ConnectorLogic; import org.apache.syncope.core.logic.ReportLogic; import org.apache.syncope.core.logic.ResourceLogic; import org.apache.syncope.core.logic.GroupLogic; import org.apache.syncope.core.logic.UserLogic; import org.apache.syncope.fit.AbstractITCase; +import org.junit.Assert; import org.junit.Test; public class LoggerITCase extends AbstractITCase { @@ -270,4 +282,85 @@ public class LoggerITCase extends AbstractITCase { assertNotNull(userLogic); assertEquals(1, IterableUtils.frequency(userLogic.getEvents(), "create")); } + + @Test + public void testCustomAuditAppender() throws IOException, InterruptedException { + InputStream propStream = null; + try { + Properties props = new Properties(); + propStream = getClass().getResourceAsStream("/core-test.properties"); + props.load(propStream); + + final String auditFilePath = props.getProperty("test.log.dir") + File.separator + + "audit_for_Master_file.log"; + final String auditNoRewriteFilePath = props.getProperty("test.log.dir") + File.separator + + "audit_for_Master_norewrite_file.log"; + // 1. Enable audit for resource update -> catched by FileRewriteAuditAppender + AuditLoggerName auditLoggerResUpd = new AuditLoggerName( + EventCategoryType.LOGIC, + ResourceLogic.class.getSimpleName(), + null, + "update", + AuditElements.Result.SUCCESS); + + LoggerTO loggerTOUpd = new LoggerTO(); + loggerTOUpd.setKey(auditLoggerResUpd.toLoggerName()); + loggerTOUpd.setLevel(LoggerLevel.DEBUG); + loggerService.update(LoggerType.AUDIT, loggerTOUpd); + // 2. Enable audit for connector update -> NOT catched by FileRewriteAuditAppender + AuditLoggerName auditLoggerConnUpd = new AuditLoggerName( + EventCategoryType.LOGIC, + ConnectorLogic.class.getSimpleName(), + null, + "update", + AuditElements.Result.SUCCESS); + + LoggerTO loggerTOConnUpd = new LoggerTO(); + loggerTOConnUpd.setKey(auditLoggerConnUpd.toLoggerName()); + loggerTOConnUpd.setLevel(LoggerLevel.DEBUG); + loggerService.update(LoggerType.AUDIT, loggerTOConnUpd); + + // 3. check that resource update is transformed and logged onto an audit file. + ResourceTO resource = resourceService.read(RESOURCE_NAME_CSV); + assertNotNull(resource); + resource.setPropagationPriority(100); + resourceService.update(resource); + + ConnInstanceTO connector = connectorService.readByResource(RESOURCE_NAME_CSV, null); + assertNotNull(connector); + connector.setPoolConf(new ConnPoolConfTO()); + connectorService.update(connector); + + File auditTempFile = new File(auditFilePath); + // check audit_for_Master_file.log, it should contain only a static message + String auditLog = FileUtils.readFileToString(auditTempFile, "UTF-8"); + + Assert.assertTrue(StringUtils.contains(auditLog, + "DEBUG Master.syncope.audit.[LOGIC]:[ResourceLogic]:[]:[update]:[SUCCESS]" + + " - This is a static test message")); + File auditNoRewriteTempFile = new File(auditNoRewriteFilePath); + // check audit_for_Master_file.log, it should contain only a static message + String auditLogNoRewrite = FileUtils.readFileToString(auditNoRewriteTempFile, "UTF-8"); + + Assert.assertFalse(StringUtils.contains(auditLogNoRewrite, + "DEBUG Master.syncope.audit.[LOGIC]:[ResourceLogic]:[]:[update]:[SUCCESS]" + + " - This is a static test message")); + + // clean audit_for_Master_file.log + FileUtils.writeStringToFile(auditTempFile, StringUtils.EMPTY, "UTF-8"); + loggerService.delete(LoggerType.AUDIT, "syncope.audit.[LOGIC]:[ResourceLogic]:[]:[update]:[SUCCESS]"); + + resource = resourceService.read(RESOURCE_NAME_CSV); + assertNotNull(resource); + resource.setPropagationPriority(200); + resourceService.update(resource); + + // check that nothing has been written to audit_for_Master_file.log + assertTrue(StringUtils.isEmpty(FileUtils.readFileToString(auditTempFile, "UTF-8"))); + } catch (IOException e) { + fail("Unable to read/write log files" + e.getMessage()); + } finally { + IOUtils.closeQuietly(propStream); + } + } } http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/fit/core-reference/src/test/resources/core-test.properties ---------------------------------------------------------------------- diff --git a/fit/core-reference/src/test/resources/core-test.properties b/fit/core-reference/src/test/resources/core-test.properties index f4e4d32..2f523e9 100644 --- a/fit/core-reference/src/test/resources/core-test.properties +++ b/fit/core-reference/src/test/resources/core-test.properties @@ -16,3 +16,4 @@ # under the License. test.csv.src=${project.build.directory}/test-classes/test.csv test.csv.dst=${test.csvdir.path}/test.csv +test.log.dir=${log.directory} http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/src/main/asciidoc/reference-guide/concepts/audit.adoc ---------------------------------------------------------------------- diff --git a/src/main/asciidoc/reference-guide/concepts/audit.adoc b/src/main/asciidoc/reference-guide/concepts/audit.adoc index 3b2b5d8..78c38d3 100644 --- a/src/main/asciidoc/reference-guide/concepts/audit.adoc +++ b/src/main/asciidoc/reference-guide/concepts/audit.adoc @@ -19,9 +19,10 @@ === Audit The audit feature allows to capture <<audit-events,events>> occurring within the <<core>> and to log relevant information -about them as entries into the `SYNCOPEAUDIT` table of the internal storage. +about them. By default events are logged as entries into the `SYNCOPEAUDIT` table of the internal storage, +but can also be logged on some additional Log4j2 appenders defined through simple customization mechanisms. -Once events are reported in the table above, they can be used as input for external tools. +Once events are reported, they can be used as input for external tools. [TIP] ==== @@ -32,3 +33,48 @@ An example of how audit entries can be extracted for reporting is shown by the < The information provided for <<notification-events,notification events>> is also valid for audit events, including examples - except for the admin console <<console-configuration-audit,tooling>>, which is naturally distinct. + +==== Audit Customization + +As mentioned above, events are, basically, logged in a database table, but this behavior can be extended through +`AuditAppender` interface implementation which allows an user to define additional logging supports that we address +as appenders. + +Appender is a Log4j entity that allows to write whatever log message on different destinations (file, queues, syslog, +other appenders, etc.). Moreover it provides the ability to edit (rewrite) the message, that is flowing through appenders, +in order to customize information. +Audit customization relies on Log4j appenders. To implement a custom audit appender an user just needs to extend to basic +classes: + +. DefaultAuditAppender +. DefaultRewriteAuditAppender + +The first is intended to add custom appender without any rewrtining of the message, the second allows message rewriting. + +What is needed to implement a well formed appender: + +. Events: a set of events to which the appender is bound. Appender will log only if one of those events occurs. +. Target Appender: the Log4j appender that writes message somewhere. See + https://github.com/andrea-patricelli/syncope/blob/2_0_X/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java[TestFileRewriteAuditAppender^] or + https://github.com/andrea-patricelli/syncope/blob/2_0_X/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java[TestFileAuditAppender^]. +. Rewrite policy (if needed): in case of rewrite enabled a rewrite policy should be defined, by implementing Log4j + `RewritePolicy` interface. Some examples are + https://github.com/andrea-patricelli/syncope/blob/2_0_X/core/logic/src/main/java/org/apache/syncope/core/logic/PassThroughRewritePolicy.java[PassThroughRewritePolicy^] + and https://github.com/andrea-patricelli/syncope/blob/2_0_X/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestRewritePolicy.java[TestRewritePolicy^]. + If no rewrite policy is specified `PassThroughRewritePolicy` will be used. + +[TIP] +==== +Be careful while assigning names to the appenders. The name of the target appender should be unique and should depend on +the domain. +A best practice is to assign different names to the appenders in order to avoid names collisions and strange behavior of +the logging framework. +==== + +===== How custom appenders work + +An appender is bound to specific events. While enabling audit on some event, if that event is "catched" also by the custom +appender, it automatically activates. Once the audit is enabled the same audit message will be logged by the +default audit appender and all the extensions bound to those events. While disabling audit all audit extensions are +disabled. +To enable an audit extension an user just needs to implement his custom `AuditAppender` in the sources, build application and deploy. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc ---------------------------------------------------------------------- diff --git a/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc b/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc index f73e360..da85daa 100644 --- a/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc +++ b/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc @@ -694,3 +694,9 @@ Moreover, `defaultValues` do not overwrite any existing value. For example, the http://www.chorevolution.eu/[CHOReVOLUTION^] IdM - based on Apache Syncope - provides https://gitlab.ow2.org/chorevolution/syncope/tree/master/ext/choreography[an extension^] for managing via the <<core>> and visualizing via the <<admin-console-component>> the running choreography instances. + +[[audit-customization]] +==== Audit Extensions + +<<audit>> by default, if enabled, logs on a specific database table, though this functionality could be extended to log +also on different supports (file, queue, syslog, etc.).