You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by mm...@apache.org on 2020/04/14 10:34:38 UTC

[syncope] branch master updated: SYNCOPE-1552: Allow WA audit events to be stored in core (#174)

This is an automated email from the ASF dual-hosted git repository.

mmoayyed pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 8f95f61  SYNCOPE-1552: Allow WA audit events to be stored in core (#174)
8f95f61 is described below

commit 8f95f612da211f8929b1be09c3516b361e4e82d5
Author: Misagh Moayyed <mm...@gmail.com>
AuthorDate: Tue Apr 14 15:04:31 2020 +0430

    SYNCOPE-1552: Allow WA audit events to be stored in core (#174)
    
    * SYNCOPE-1552: Allow WA audit events to be stored in core
    
    * SYNCOPE-1552: quiet logs
    
    * SYNCOPE-1552: quiet logs
    
    * SYNCOPE-1552: quiet logs
    
    * SYNCOPE-1552: address review + fix test
---
 .travis.yml                                        |  2 +-
 .../client/console/rest/LoggerRestClient.java      |  2 +-
 .../syncope/common/lib/types/AuditElements.java    |  1 +
 .../common/lib/types/IdRepoEntitlement.java        |  2 +
 .../common/rest/api/service/LoggerService.java     | 11 +++
 .../org/apache/syncope/core/logic/LoggerLogic.java | 27 ++++++-
 .../core/rest/cxf/service/LoggerServiceImpl.java   |  5 ++
 .../core/persistence/api/dao/LoggerDAO.java        |  7 +-
 .../src/main/resources/domains/MasterContent.xml   |  2 -
 .../core/persistence/jpa/dao/JPALoggerDAO.java     | 21 ++++-
 .../src/test/resources/domains/MasterContent.xml   |  2 +
 .../org/apache/syncope/fit/core/LoggerITCase.java  | 32 ++++++++
 pom.xml                                            |  5 ++
 wa/starter/pom.xml                                 |  6 +-
 .../wa/starter/SyncopeWAAuditTrailManager.java     | 94 ++++++++++++++++++++++
 .../syncope/wa/starter/SyncopeWAConfiguration.java | 17 +++-
 16 files changed, 221 insertions(+), 15 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index d1bdc63..9bf34eb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -71,7 +71,7 @@ jobs:
       ######################################################
     - stage: fit
       name: "Full Integration Tests: Apache Tomcat / H2 / JSON Content-Type"
-      script: mvn -f fit/core-reference/pom.xml verify -Dinvoker.streamLogs=true -Dmodernizer.skip=true -Dianal.skip=true -Drat.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true
+      script: travis_wait 55 mvn -f fit/core-reference/pom.xml verify --quiet -Dinvoker.streamLogs=true -Dmodernizer.skip=true -Dianal.skip=true -Drat.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true
       after_failure:
        - cat fit/core-reference/target/log/*
        - cat fit/core-reference/target/failsafe-reports/org.apache.syncope.fit.*-output.txt
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java
index 462b608..aefccb9 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java
@@ -57,7 +57,7 @@ public class LoggerRestClient extends BaseRestClient {
 
         return result;
     }
-
+    
     public static List<LoggerTO> listLogs() {
         List<LoggerTO> logs = getService(LoggerService.class).list(LoggerType.LOG);
         logs.sort(Comparator.comparing(LoggerTO::getKey));
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditElements.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditElements.java
index 32e87a2..e1116bd 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditElements.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditElements.java
@@ -33,6 +33,7 @@ public final class AuditElements implements Serializable {
     public enum EventCategoryType {
 
         LOGIC("LOGIC"),
+        WA("WA"),
         TASK("TASK"),
         PROPAGATION("PropagationTask"),
         PULL("PullTask"),
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java
index 8ab42af..2fdec6b 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java
@@ -212,6 +212,8 @@ public final class IdRepoEntitlement {
 
     public static final String AUDIT_READ = "AUDIT_READ";
 
+    public static final String AUDIT_CREATE = "AUDIT_CREATE";
+
     public static final String AUDIT_ENABLE = "AUDIT_ENABLE";
 
     public static final String AUDIT_DISABLE = "AUDIT_DISABLE";
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/LoggerService.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/LoggerService.java
index 1b494a3..d54f690 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/LoggerService.java
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/LoggerService.java
@@ -32,6 +32,7 @@ import javax.ws.rs.BeanParam;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
+import javax.ws.rs.POST;
 import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
@@ -100,6 +101,16 @@ public interface LoggerService extends JAXRSService {
     PagedResult<AuditEntry> search(@BeanParam AuditQuery auditQuery);
 
     /**
+     * Create an audit entry.
+     *
+     * @param auditEntry audit entry to persist.
+     */
+    @POST
+    @Path("AUDIT/entries")
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    void create(@NotNull AuditEntry auditEntry);
+    
+    /**
      * Returns logger with matching type and name.
      *
      * @param type LoggerType to be selected.
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
index 3b1fe49..c450ce5 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
@@ -59,6 +59,7 @@ import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.Logger;
+import org.apache.syncope.core.provisioning.api.AuditManager;
 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;
@@ -98,6 +99,9 @@ public class LoggerLogic extends AbstractTransactionalLogic<EntityTO> {
     @Autowired
     private LoggerDataBinder binder;
 
+    @Autowired
+    private AuditManager auditManager;
+    
     @PreAuthorize("hasRole('" + IdRepoEntitlement.LOG_LIST + "') and authentication.details.domain == "
             + "T(org.apache.syncope.common.lib.SyncopeConstants).MASTER_DOMAIN")
     @Transactional(readOnly = true)
@@ -409,12 +413,33 @@ public class LoggerLogic extends AbstractTransactionalLogic<EntityTO> {
             final AuditElements.Result result,
             final List<OrderByClause> orderByClauses) {
 
-        int count = loggerDAO.countAuditEntries(entityKey);
+        int count = loggerDAO.countAuditEntries(entityKey, type, category, subcategory, events, result);
         List<AuditEntry> matching = loggerDAO.findAuditEntries(
                 entityKey, page, size, type, category, subcategory, events, result, orderByClauses);
         return Pair.of(count, matching);
     }
 
+    @PreAuthorize("hasRole('" + IdRepoEntitlement.AUDIT_CREATE + "')")
+    public void create(final AuditEntry auditEntry) {
+        boolean auditRequested = auditManager.auditRequested(auditEntry.getWho(),
+            auditEntry.getLogger().getType(),
+            auditEntry.getLogger().getCategory(),
+            auditEntry.getLogger().getSubcategory(),
+            auditEntry.getLogger().getEvent());
+
+        if (auditRequested) {
+            auditManager.audit(auditEntry.getWho(),
+                auditEntry.getLogger().getType(),
+                auditEntry.getLogger().getCategory(),
+                auditEntry.getLogger().getSubcategory(),
+                auditEntry.getLogger().getEvent(),
+                auditEntry.getLogger().getResult(),
+                auditEntry.getBefore(),
+                auditEntry.getOutput(),
+                auditEntry.getInputs());
+        }
+    }
+
     @Override
     protected EntityTO resolveReference(final Method method, final Object... args)
             throws UnresolvedReferenceException {
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/LoggerServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/LoggerServiceImpl.java
index b8ab5b4..9367c9d 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/LoggerServiceImpl.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/LoggerServiceImpl.java
@@ -52,6 +52,11 @@ public class LoggerServiceImpl extends AbstractServiceImpl implements LoggerServ
     public List<LogStatement> getLastLogStatements(final String memoryAppender) {
         return logic.getLastLogStatements(memoryAppender);
     }
+    
+    @Override
+    public void create(final AuditEntry auditEntry) {
+        logic.create(auditEntry);
+    }
 
     @Override
     public void delete(final LoggerType type, final String name) {
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/LoggerDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/LoggerDAO.java
index e8cfe5a..e7e1b43 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/LoggerDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/LoggerDAO.java
@@ -52,5 +52,10 @@ public interface LoggerDAO extends DAO<Logger> {
             AuditElements.Result result,
             List<OrderByClause> orderByClauses);
 
-    int countAuditEntries(String entityKey);
+    int countAuditEntries(String entityKey,
+                          AuditElements.EventCategoryType type,
+                          String category,
+                          String subcategory,
+                          List<String> events,
+                          AuditElements.Result result);
 }
diff --git a/core/persistence-jpa-json/src/main/resources/domains/MasterContent.xml b/core/persistence-jpa-json/src/main/resources/domains/MasterContent.xml
index 9ae7908..b987957 100644
--- a/core/persistence-jpa-json/src/main/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/main/resources/domains/MasterContent.xml
@@ -20,8 +20,6 @@ under the License.
 <dataset>
   <Realm id="ea696a4f-e77a-4ef1-be67-8f8093bc8686" name="/"/>  
 
-<<<<<<< HEAD
-=======
   <SyncopeSchema id="password.cipher.algorithm"/>
   <PlainSchema id="password.cipher.algorithm" type="String"
                mandatoryCondition="true" multivalue="0" uniqueConstraint="0" readonly="0"/>
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPALoggerDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPALoggerDAO.java
index 6e85952..cced968 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPALoggerDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPALoggerDAO.java
@@ -44,8 +44,12 @@ public class JPALoggerDAO extends AbstractDAO<Logger> implements LoggerDAO {
         protected final StringBuilder query = new StringBuilder();
 
         protected MessageCriteriaBuilder entityKey(final String entityKey) {
-            query.append(' ').append(AUDIT_MESSAGE_COLUMN).
+            if (entityKey != null) {
+                query.append(' ').append(AUDIT_MESSAGE_COLUMN).
                     append(" LIKE '%key%").append(entityKey).append("%'");
+            } else {
+                query.append(" 1=1");
+            }
             return this;
         }
 
@@ -139,9 +143,20 @@ public class JPALoggerDAO extends AbstractDAO<Logger> implements LoggerDAO {
     }
 
     @Override
-    public int countAuditEntries(final String entityKey) {
+    public int countAuditEntries(final String entityKey,
+                                 final AuditElements.EventCategoryType type,
+                                 final String category,
+                                 final String subcategory,
+                                 final List<String> events,
+                                 final AuditElements.Result result) {
         String queryString = "SELECT COUNT(0) FROM " + AUDIT_TABLE
-                + " WHERE " + messageCriteriaBuilder(entityKey).build();
+            + " WHERE " + messageCriteriaBuilder(entityKey).
+            type(type).
+            category(category).
+            subcategory(subcategory).
+            result(result).
+            events(events).
+            build();
         Query countQuery = entityManager().createNativeQuery(queryString);
 
         return ((Number) countQuery.getSingleResult()).intValue();
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index a222994..dc7369d 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -2472,6 +2472,8 @@ $$ }&#10;
 
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[SyncopeLogic]:[]:[isSelfRegAllowed]:[SUCCESS]" logLevel="DEBUG"/>
 
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[WA]:[LoggerLogic]:[AUTHENTICATION]:[validate]:[SUCCESS]" logLevel="DEBUG"/>
+
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[ConnectorLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[ConnectorLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
 
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 7857c71..fea40c9 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
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 
 import java.io.File;
 import java.io.IOException;
@@ -33,18 +34,23 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardOpenOption;
 import java.text.ParseException;
+import java.util.Date;
 import java.util.List;
 import java.util.Properties;
+import java.util.UUID;
+
 import javax.ws.rs.core.Response;
 import javax.xml.ws.WebServiceException;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.log.AuditEntry;
 import org.apache.syncope.common.lib.log.EventCategory;
 import org.apache.syncope.common.lib.log.LogAppender;
 import org.apache.syncope.common.lib.log.LogStatement;
 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.PagedResult;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.common.lib.to.ResourceTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
@@ -57,6 +63,7 @@ import org.apache.syncope.common.lib.types.MatchingRule;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
 import org.apache.syncope.common.rest.api.LoggerWrapper;
+import org.apache.syncope.common.rest.api.beans.AuditQuery;
 import org.apache.syncope.common.rest.api.beans.ReconQuery;
 import org.apache.syncope.core.logic.ConnectorLogic;
 import org.apache.syncope.core.logic.ReportLogic;
@@ -268,6 +275,31 @@ public class LoggerITCase extends AbstractITCase {
     }
 
     @Test
+    public void saveAuditEvent() {
+        AuditLoggerName logger = new AuditLoggerName(EventCategoryType.WA, "LoggerLogic",
+            AuditElements.AUTHENTICATION_CATEGORY.toUpperCase(), "validate",
+            AuditElements.Result.SUCCESS);
+        AuditEntry auditEntry = new AuditEntry();
+        String who = "syncope-user " + UUID.randomUUID().toString();
+        auditEntry.setWho(who);
+        auditEntry.setLogger(logger);
+        auditEntry.setDate(new Date());
+        auditEntry.setBefore(UUID.randomUUID().toString());
+        auditEntry.setOutput(UUID.randomUUID().toString());
+        assertDoesNotThrow(() -> loggerService.create(auditEntry));
+
+        AuditQuery query = new AuditQuery();
+        query.setSize(1);
+        query.setType(auditEntry.getLogger().getType());
+        query.setResult(auditEntry.getLogger().getResult());
+        query.setCategory(auditEntry.getLogger().getCategory());
+        query.setEvents(List.of(auditEntry.getLogger().getEvent()));
+        PagedResult<AuditEntry> events = loggerService.search(query);
+        assertNotNull(events);
+        assertEquals(1, events.getSize());
+    }
+
+    @Test
     public void customAuditAppender() throws IOException, InterruptedException {
         AuditLoggerName auditLoggerResUpd = new AuditLoggerName(
                 EventCategoryType.LOGIC,
diff --git a/pom.xml b/pom.xml
index d62aaf0..dd80e72 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1585,6 +1585,11 @@ under the License.
       </dependency>
       <dependency>
         <groupId>org.apereo.cas</groupId>
+        <artifactId>cas-server-core-audit-api</artifactId>
+        <version>${cas.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apereo.cas</groupId>
         <artifactId>cas-server-support-saml-idp</artifactId>
         <version>${cas.version}</version>
       </dependency>
diff --git a/wa/starter/pom.xml b/wa/starter/pom.xml
index afd48ac..5d0fe8a 100644
--- a/wa/starter/pom.xml
+++ b/wa/starter/pom.xml
@@ -43,13 +43,11 @@ under the License.
       <artifactId>syncope-wa-bootstrap</artifactId>
       <version>${project.version}</version>
     </dependency>
-
     <dependency>
       <groupId>org.apache.syncope.common.keymaster</groupId>
       <artifactId>syncope-common-keymaster-client-api</artifactId>
       <version>${project.version}</version>
     </dependency>
-
     <dependency>
       <groupId>org.apache.syncope.client.am</groupId>
       <artifactId>syncope-client-am-lib</artifactId>
@@ -74,6 +72,10 @@ under the License.
     </dependency>
     <dependency>
       <groupId>org.apereo.cas</groupId>
+      <artifactId>cas-server-core-audit-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apereo.cas</groupId>
       <artifactId>cas-server-core-cookie</artifactId>
     </dependency>
     <dependency>
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAAuditTrailManager.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAAuditTrailManager.java
new file mode 100644
index 0000000..7dbd561
--- /dev/null
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAAuditTrailManager.java
@@ -0,0 +1,94 @@
+/*
+ * 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.wa.starter;
+
+import org.apereo.cas.audit.spi.AbstractAuditTrailManager;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.log.AuditEntry;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.common.rest.api.service.LoggerService;
+import org.apache.syncope.wa.WARestClient;
+import org.apereo.inspektr.audit.AuditActionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.LocalDate;
+import java.util.Map;
+import java.util.Set;
+
+public class SyncopeWAAuditTrailManager extends AbstractAuditTrailManager {
+    private static final Logger LOG = LoggerFactory.getLogger(SyncopeWAAuditTrailManager.class);
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    private final WARestClient restClient;
+
+    SyncopeWAAuditTrailManager(final WARestClient restClient) {
+        super(true);
+        this.restClient = restClient;
+    }
+
+    @Override
+    protected void saveAuditRecord(final AuditActionContext audit) {
+        if (!WARestClient.isReady()) {
+            LOG.debug("Syncope client is not yet ready to store audit record");
+            return;
+        }
+
+        LOG.info("Loading application definitions");
+        LoggerService loggerService = restClient.getSyncopeClient().
+            getService(LoggerService.class);
+
+        try {             
+            String output = OBJECT_MAPPER.writeValueAsString(Map.of("resource", audit.getResourceOperatedUpon(),
+                "clientIpAddress", audit.getClientIpAddress(),
+                "serverIpAddress", audit.getServerIpAddress()));
+
+            AuditEntry auditEntry = new AuditEntry();
+            auditEntry.setWho(audit.getPrincipal());
+            auditEntry.setDate(audit.getWhenActionWasPerformed());
+            auditEntry.setOutput(output);
+            AuditElements.Result result = StringUtils.containsIgnoreCase(audit.getActionPerformed(), "fail")
+                ? AuditElements.Result.FAILURE
+                : AuditElements.Result.SUCCESS;
+
+            AuditLoggerName auditLogger = new AuditLoggerName(AuditElements.EventCategoryType.WA,
+                "LoggerLogic", AuditElements.AUTHENTICATION_CATEGORY.toUpperCase(),
+                audit.getActionPerformed(), result);
+
+            auditEntry.setLogger(auditLogger);
+            loggerService.create(auditEntry);
+        } catch (Exception e) {
+            LOG.error("During serialization", e);
+        }
+    }
+
+    @Override
+    public Set<? extends AuditActionContext> getAuditRecordsSince(final LocalDate sinceDate) {
+        throw new UnsupportedOperationException("Fetching audit events from WA is not supported");
+    }
+
+    @Override
+    public void removeAll() {
+        throw new UnsupportedOperationException("Removing audit events from WA is not supported");
+    }
+}
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java
index 938eb39..8b61f58 100644
--- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java
@@ -18,19 +18,22 @@
  */
 package org.apache.syncope.wa.starter;
 
-import java.util.Collection;
+import org.apereo.cas.audit.AuditTrailExecutionPlanConfigurer;
+import org.apereo.cas.services.ServiceRegistryExecutionPlanConfigurer;
+import org.apereo.cas.services.ServiceRegistryListener;
+
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
 import org.apache.syncope.wa.WARestClient;
-import org.apereo.cas.services.ServiceRegistryExecutionPlanConfigurer;
-import org.apereo.cas.services.ServiceRegistryListener;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+import java.util.Collection;
+
 @Configuration
 public class SyncopeWAConfiguration {
 
@@ -45,11 +48,17 @@ public class SyncopeWAConfiguration {
     @Bean
     public ServiceRegistryExecutionPlanConfigurer syncopeServiceRegistryConfigurer(final WARestClient restClient) {
         SyncopeServiceRegistry registry =
-                new SyncopeServiceRegistry(restClient, applicationContext, serviceRegistryListeners);
+            new SyncopeServiceRegistry(restClient, applicationContext, serviceRegistryListeners);
         return plan -> plan.registerServiceRegistry(registry);
     }
 
     @Bean
+    @Autowired
+    public AuditTrailExecutionPlanConfigurer auditConfigurer(final WARestClient restClient) {
+        return plan -> plan.registerAuditTrailManager(new SyncopeWAAuditTrailManager(restClient));
+    }
+
+    @Bean
     public KeymasterStart keymasterStart() {
         return new KeymasterStart(NetworkService.Type.WA);
     }