You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2023/12/12 16:14:47 UTC

(syncope) branch 3_0_X updated: [SYNCOPE-1791] Improving Audit conf management

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

ilgrosso pushed a commit to branch 3_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/3_0_X by this push:
     new ad28f966f0 [SYNCOPE-1791] Improving Audit conf management
ad28f966f0 is described below

commit ad28f966f09fd73d6bf5a45f6ee1661ce3234de5
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Tue Dec 12 16:50:11 2023 +0100

    [SYNCOPE-1791] Improving Audit conf management
---
 .../client/console/events/EventCategoryPanel.java  | 338 +++++++++++----------
 .../client/console/events/EventSelectionPanel.java |  76 ++---
 .../client/console/events/SelectedEventsPanel.java |  46 +--
 .../notifications/NotificationDirectoryPanel.java  |  18 +-
 .../notifications/NotificationWizardBuilder.java   |  12 +-
 .../apache/syncope/client/console/pages/Audit.java |  39 +--
 .../client/console/rest/AuditRestClient.java       |   4 +-
 .../syncope/common/lib/types/AuditLoggerName.java  | 203 ++++++-------
 .../common/lib/types/AuditLoggerNameTest.java      |  36 +++
 .../syncope/core/logic/IdMLogicTestContext.java    |   6 +
 .../org/apache/syncope/core/logic/AuditLogic.java  |  60 ++--
 .../syncope/core/logic/IdRepoLogicContext.java     |  11 +-
 .../syncope/core/logic/init/AuditAccessor.java     |  30 +-
 .../syncope/core/logic/init/AuditLoader.java       |  32 +-
 .../syncope/core/logic/IdRepoLogicTestContext.java |   6 +
 .../java/pushpull/LDAPMembershipPullActions.java   |   8 +-
 .../pushpull/LDAPMembershipPullActionsTest.java    |  35 +--
 .../org/apache/syncope/fit/core/AuditITCase.java   |  80 ++++-
 .../test/resources/CustomAuditLogicActions.groovy  |  47 +++
 19 files changed, 599 insertions(+), 488 deletions(-)

diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/events/EventCategoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/events/EventCategoryPanel.java
index cbad77a315..983f259af3 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/events/EventCategoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/events/EventCategoryPanel.java
@@ -18,10 +18,9 @@
  */
 package org.apache.syncope.client.console.events;
 
-import java.util.HashSet;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
@@ -55,9 +54,54 @@ public abstract class EventCategoryPanel extends Panel {
 
     private static final long serialVersionUID = 6429053774964787734L;
 
-    private final List<EventCategory> eventCategoryTOs;
+    protected static class ChangeCategoryEvent {
 
-    private final EventCategory eventCategoryTO = new EventCategory();
+        protected final AjaxRequestTarget target;
+
+        protected final Panel changedPanel;
+
+        ChangeCategoryEvent(final AjaxRequestTarget target, final Panel changedPanel) {
+            this.target = target;
+            this.changedPanel = changedPanel;
+        }
+
+        public AjaxRequestTarget getTarget() {
+            return target;
+        }
+
+        public Panel getChangedPanel() {
+            return changedPanel;
+        }
+    }
+
+    protected static List<String> filter(
+            final List<EventCategory> categories,
+            final EventCategoryType type) {
+
+        return categories.stream().
+                filter(c -> type == c.getType() && StringUtils.isNotEmpty(c.getCategory())).
+                map(EventCategory::getCategory).
+                distinct().
+                sorted().
+                collect(Collectors.toList());
+    }
+
+    protected static List<String> filter(
+            final List<EventCategory> categories,
+            final EventCategoryType type,
+            final String category) {
+
+        return categories.stream().
+                filter(c -> type == c.getType() && StringUtils.equals(category, c.getCategory())).
+                map(EventCategory::getCategory).
+                distinct().
+                sorted().
+                collect(Collectors.toList());
+    }
+
+    private final List<EventCategory> eventCategories;
+
+    private final EventCategory eventCategory = new EventCategory();
 
     private final WebMarkupContainer categoryContainer;
 
@@ -79,7 +123,7 @@ public abstract class EventCategoryPanel extends Panel {
 
     public EventCategoryPanel(
             final String id,
-            final List<EventCategory> eventCategoryTOs,
+            final List<EventCategory> eventCategories,
             final IModel<List<String>> model) {
 
         super(id);
@@ -88,7 +132,7 @@ public abstract class EventCategoryPanel extends Panel {
         selectedEventsPanel = new SelectedEventsPanel("selectedEventsPanel", model);
         add(selectedEventsPanel);
 
-        this.eventCategoryTOs = eventCategoryTOs;
+        this.eventCategories = eventCategories;
 
         categoryContainer = new WebMarkupContainer("categoryContainer");
         categoryContainer.setOutputMarkupId(true);
@@ -104,9 +148,12 @@ public abstract class EventCategoryPanel extends Panel {
         type = new AjaxDropDownChoicePanel<>(
                 "type",
                 "type",
-                new PropertyModel<>(eventCategoryTO, "type"),
+                new PropertyModel<>(eventCategory, "type"),
                 false);
-        type.setChoices(List.of(EventCategoryType.values()));
+        type.setChoices(eventCategories.stream().
+                map(EventCategory::getType).distinct().
+                sorted(Comparator.comparing(EventCategoryType::name)).
+                collect(Collectors.toList()));
         type.setChoiceRenderer(new IChoiceRenderer<>() {
 
             private static final long serialVersionUID = 2317134950949778735L;
@@ -123,7 +170,9 @@ public abstract class EventCategoryPanel extends Panel {
 
             @Override
             public EventCategoryType getObject(
-                final String id, final IModel<? extends List<? extends EventCategoryType>> choices) {
+                    final String id,
+                    final IModel<? extends List<? extends EventCategoryType>> choices) {
+
                 return choices.getObject().stream().filter(object -> object.name().equals(id)).findAny().orElse(null);
             }
         });
@@ -142,9 +191,9 @@ public abstract class EventCategoryPanel extends Panel {
         category = new AjaxDropDownChoicePanel<>(
                 "category",
                 "category",
-                new PropertyModel<>(eventCategoryTO, "category"),
+                new PropertyModel<>(eventCategory, "category"),
                 false);
-        category.setChoices(filter(eventCategoryTOs, type.getModelObject()));
+        category.setChoices(filter(eventCategories, type.getModelObject()));
         categoryContainer.add(category);
 
         category.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
@@ -160,9 +209,9 @@ public abstract class EventCategoryPanel extends Panel {
         subcategory = new AjaxDropDownChoicePanel<>(
                 "subcategory",
                 "subcategory",
-                new PropertyModel<>(eventCategoryTO, "subcategory"),
+                new PropertyModel<>(eventCategory, "subcategory"),
                 false);
-        subcategory.setChoices(filter(eventCategoryTOs, type.getModelObject(), category.getModelObject()));
+        subcategory.setChoices(filter(eventCategories, type.getModelObject(), category.getModelObject()));
         categoryContainer.add(subcategory);
 
         subcategory.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
@@ -191,22 +240,22 @@ public abstract class EventCategoryPanel extends Panel {
             @Override
             public void onClick(final AjaxRequestTarget target, final EventCategory ignore) {
                 if (StringUtils.isNotBlank(custom.getModelObject())) {
-                    Pair<EventCategory, AuditElements.Result> parsed = AuditLoggerName.parseEventCategory(custom.
-                        getModelObject());
+                    Pair<EventCategory, AuditElements.Result> parsed =
+                            AuditLoggerName.parseEventCategory(custom.getModelObject());
 
-                    String eventString = AuditLoggerName.buildEvent(
-                        parsed.getKey().getType(),
-                        null,
-                        null,
-                        parsed.getKey().getEvents().isEmpty()
-                            ? StringUtils.EMPTY : parsed.getKey().getEvents().iterator().next(),
-                        parsed.getValue());
+                    AuditLoggerName auditLoggerName = new AuditLoggerName(
+                            parsed.getLeft().getType(),
+                            null,
+                            null,
+                            parsed.getLeft().getEvents().isEmpty()
+                            ? StringUtils.EMPTY : parsed.getLeft().getEvents().iterator().next(),
+                            parsed.getRight());
 
                     custom.setModelObject(StringUtils.EMPTY);
                     send(EventCategoryPanel.this.getPage(), Broadcast.BREADTH, new EventSelectionChanged(
-                        target,
-                        Set.of(eventString),
-                        Set.of()));
+                            target,
+                            Set.of(auditLoggerName),
+                            Set.of()));
                     target.add(categoryContainer);
                 }
             }
@@ -218,22 +267,22 @@ public abstract class EventCategoryPanel extends Panel {
             @Override
             public void onClick(final AjaxRequestTarget target, final EventCategory ignore) {
                 if (StringUtils.isNotBlank(custom.getModelObject())) {
-                    Pair<EventCategory, AuditElements.Result> parsed = AuditLoggerName.parseEventCategory(custom.
-                        getModelObject());
+                    Pair<EventCategory, AuditElements.Result> parsed =
+                            AuditLoggerName.parseEventCategory(custom.getModelObject());
 
-                    String eventString = AuditLoggerName.buildEvent(
-                        parsed.getKey().getType(),
-                        null,
-                        null,
-                        parsed.getKey().getEvents().isEmpty()
-                            ? StringUtils.EMPTY : parsed.getKey().getEvents().iterator().next(),
-                        parsed.getValue());
+                    AuditLoggerName auditLoggerName = new AuditLoggerName(
+                            parsed.getLeft().getType(),
+                            null,
+                            null,
+                            parsed.getLeft().getEvents().isEmpty()
+                            ? StringUtils.EMPTY : parsed.getLeft().getEvents().iterator().next(),
+                            parsed.getRight());
 
                     custom.setModelObject(StringUtils.EMPTY);
                     send(EventCategoryPanel.this.getPage(), Broadcast.BREADTH, new EventSelectionChanged(
-                        target,
-                        Set.of(),
-                        Set.of(eventString)));
+                            target,
+                            Set.of(),
+                            Set.of(auditLoggerName)));
                     target.add(categoryContainer);
                 }
             }
@@ -245,7 +294,7 @@ public abstract class EventCategoryPanel extends Panel {
         actionsPanel.setEnabled(false);
         actionsPanel.setMarkupId("inline-actions");
 
-        eventsContainer.add(new EventSelectionPanel("eventsPanel", eventCategoryTO, model) {
+        eventsContainer.add(new EventSelectionPanel("eventsPanel", eventCategory, model) {
 
             private static final long serialVersionUID = 3513194801190026082L;
 
@@ -256,106 +305,84 @@ public abstract class EventCategoryPanel extends Panel {
         });
     }
 
-    private static List<String> filter(final List<EventCategory> eventCategoryTOs, final EventCategoryType type) {
-        Set<String> res = new HashSet<>();
-
-        eventCategoryTOs.stream().filter(eventCategory
-                -> type == eventCategory.getType() && StringUtils.isNotEmpty(eventCategory.getCategory())).
-                forEachOrdered(eventCategory -> res.add(eventCategory.getCategory()));
-
-        return res.stream().sorted().collect(Collectors.toList());
-    }
-
-    private static List<String> filter(
-            final List<EventCategory> eventCategoryTOs, final EventCategoryType type, final String category) {
-
-        Set<String> res = new HashSet<>();
-
-        eventCategoryTOs.stream().filter(eventCategory
-                -> type == eventCategory.getType() && StringUtils.equals(category, eventCategory.getCategory())
-                && StringUtils.isNotEmpty(eventCategory.getSubcategory())).
-                forEachOrdered(eventCategory -> res.add(eventCategory.getSubcategory()));
-
-        return res.stream().sorted().collect(Collectors.toList());
-    }
-
     @Override
     public void onEvent(final IEvent<?> event) {
         if (event.getPayload() instanceof ChangeCategoryEvent) {
             // update objects ....
-            eventCategoryTO.getEvents().clear();
-
-            final ChangeCategoryEvent change = (ChangeCategoryEvent) event.getPayload();
-
-            final Panel changedPanel = change.getChangedPanel();
-            if (null != changedPanel.getId()) {
-                switch (changedPanel.getId()) {
-                    case "type":
-                        eventCategoryTO.setType(type.getModelObject());
-                        eventCategoryTO.setCategory(null);
-                        eventCategoryTO.setSubcategory(null);
-                        if (type.getModelObject() == EventCategoryType.CUSTOM) {
-                            category.setChoices(List.of());
-                            subcategory.setChoices(List.of());
-                            category.setEnabled(false);
-                            subcategory.setEnabled(false);
-                            custom.setVisible(true);
-                            custom.setEnabled(true);
-                            actionsPanel.setVisible(true);
-                            actionsPanel.setEnabled(true);
-                        } else {
-                            category.setChoices(filter(eventCategoryTOs, type.getModelObject()));
-                            subcategory.setChoices(List.of());
-                            category.setEnabled(true);
-                            subcategory.setEnabled(true);
-                            custom.setVisible(false);
-                            custom.setEnabled(false);
-                            actionsPanel.setVisible(false);
-                            actionsPanel.setEnabled(false);
-                        }
-                        change.getTarget().add(categoryContainer);
-                        break;
-
-                    case "category":
-                        subcategory.setChoices(
-                                filter(eventCategoryTOs, type.getModelObject(), category.getModelObject()));
-                        eventCategoryTO.setCategory(category.getModelObject());
-                        eventCategoryTO.setSubcategory(null);
-                        change.getTarget().add(categoryContainer);
-                        break;
-
-                    default:
-                        eventCategoryTO.setSubcategory(subcategory.getModelObject());
-                        break;
-                }
+            eventCategory.getEvents().clear();
+
+            ChangeCategoryEvent change = (ChangeCategoryEvent) event.getPayload();
+
+            switch (change.getChangedPanel().getId()) {
+                case "type":
+                    eventCategory.setType(type.getModelObject());
+                    eventCategory.setCategory(null);
+                    eventCategory.setSubcategory(null);
+                    if (type.getModelObject() == EventCategoryType.CUSTOM
+                            || type.getModelObject() == EventCategoryType.WA) {
+
+                        category.setChoices(List.of());
+                        subcategory.setChoices(List.of());
+                        category.setEnabled(false);
+                        subcategory.setEnabled(false);
+                        custom.setVisible(true);
+                        custom.setEnabled(true);
+                        actionsPanel.setVisible(true);
+                        actionsPanel.setEnabled(true);
+                    } else {
+                        category.setChoices(filter(eventCategories, type.getModelObject()));
+                        subcategory.setChoices(List.of());
+                        category.setEnabled(true);
+                        subcategory.setEnabled(true);
+                        custom.setVisible(false);
+                        custom.setEnabled(false);
+                        actionsPanel.setVisible(false);
+                        actionsPanel.setEnabled(false);
+                    }
+                    change.getTarget().add(categoryContainer);
+                    break;
+
+                case "category":
+                    subcategory.setChoices(
+                            filter(eventCategories, type.getModelObject(), category.getModelObject()));
+                    eventCategory.setCategory(category.getModelObject());
+                    eventCategory.setSubcategory(null);
+                    change.getTarget().add(categoryContainer);
+                    break;
+
+                default:
+                    eventCategory.setSubcategory(subcategory.getModelObject());
+                    break;
             }
 
             updateEventsContainer(change.getTarget());
         } else if (event.getPayload() instanceof InspectSelectedEvent) {
             // update objects ....
-            eventCategoryTO.getEvents().clear();
+            eventCategory.getEvents().clear();
 
             InspectSelectedEvent inspectSelectedEvent = (InspectSelectedEvent) event.getPayload();
 
-            Map.Entry<EventCategory, AuditElements.Result> categoryEvent =
+            Pair<EventCategory, AuditElements.Result> categoryEvent =
                     AuditLoggerName.parseEventCategory(inspectSelectedEvent.getEvent());
 
-            eventCategoryTO.setType(categoryEvent.getKey().getType());
-            category.setChoices(filter(eventCategoryTOs, type.getModelObject()));
+            eventCategory.setType(categoryEvent.getLeft().getType());
+            category.setChoices(filter(eventCategories, type.getModelObject()));
 
-            eventCategoryTO.setCategory(categoryEvent.getKey().getCategory());
-            subcategory.setChoices(filter(eventCategoryTOs, type.getModelObject(), category.getModelObject()));
+            eventCategory.setCategory(categoryEvent.getLeft().getCategory());
+            subcategory.setChoices(filter(eventCategories, type.getModelObject(), category.getModelObject()));
 
-            eventCategoryTO.setSubcategory(categoryEvent.getKey().getSubcategory());
+            eventCategory.setSubcategory(categoryEvent.getLeft().getSubcategory());
+
+            if (categoryEvent.getLeft().getType() == EventCategoryType.CUSTOM
+                    || categoryEvent.getLeft().getType() == EventCategoryType.WA) {
 
-            if (categoryEvent.getKey().getType() == EventCategoryType.CUSTOM) {
                 custom.setModelObject(AuditLoggerName.buildEvent(
-                        categoryEvent.getKey().getType(),
-                        categoryEvent.getKey().getCategory(),
-                        categoryEvent.getKey().getSubcategory(),
-                        categoryEvent.getKey().getEvents().isEmpty()
-                        ? StringUtils.EMPTY : categoryEvent.getKey().getEvents().iterator().next(),
-                        categoryEvent.getValue()));
+                        categoryEvent.getLeft().getType(),
+                        categoryEvent.getLeft().getCategory(),
+                        categoryEvent.getLeft().getSubcategory(),
+                        categoryEvent.getLeft().getEvents().isEmpty()
+                        ? StringUtils.EMPTY : categoryEvent.getLeft().getEvents().iterator().next(),
+                        categoryEvent.getRight()));
 
                 category.setEnabled(false);
                 subcategory.setEnabled(false);
@@ -377,40 +404,6 @@ public abstract class EventCategoryPanel extends Panel {
         }
     }
 
-    private void setEvents() {
-        for (Iterator<EventCategory> itor = eventCategoryTOs.iterator();
-                itor.hasNext() && eventCategoryTO.getEvents().isEmpty();) {
-
-            EventCategory eventCategory = itor.next();
-            if (eventCategory.getType() == eventCategoryTO.getType()
-                    && StringUtils.equals(eventCategory.getCategory(), eventCategoryTO.getCategory())
-                    && StringUtils.equals(eventCategory.getSubcategory(), eventCategoryTO.getSubcategory())) {
-
-                eventCategoryTO.getEvents().addAll(eventCategory.getEvents());
-            }
-        }
-    }
-
-    private static class ChangeCategoryEvent {
-
-        private final AjaxRequestTarget target;
-
-        private final Panel changedPanel;
-
-        ChangeCategoryEvent(final AjaxRequestTarget target, final Panel changedPanel) {
-            this.target = target;
-            this.changedPanel = changedPanel;
-        }
-
-        public AjaxRequestTarget getTarget() {
-            return target;
-        }
-
-        public Panel getChangedPanel() {
-            return changedPanel;
-        }
-    }
-
     /**
      * To be extended in order to add actions on events.
      *
@@ -420,22 +413,35 @@ public abstract class EventCategoryPanel extends Panel {
         // nothing by default
     }
 
-    private void authorizeList() {
-        getListAuthRoles().forEach(
-                role -> MetaDataRoleAuthorizationStrategy.authorize(selectedEventsPanel, RENDER, role));
+    protected abstract List<String> getListAuthRoles();
+
+    protected void authorizeList() {
+        getListAuthRoles().forEach(r -> MetaDataRoleAuthorizationStrategy.authorize(selectedEventsPanel, RENDER, r));
     }
 
-    private void authorizeChanges() {
-        getChangeAuthRoles().forEach(role -> {
-            MetaDataRoleAuthorizationStrategy.authorize(categoryContainer, RENDER, role);
-            MetaDataRoleAuthorizationStrategy.authorize(eventsContainer, RENDER, role);
+    protected abstract List<String> getChangeAuthRoles();
+
+    protected void authorizeChanges() {
+        getChangeAuthRoles().forEach(r -> {
+            MetaDataRoleAuthorizationStrategy.authorize(categoryContainer, RENDER, r);
+            MetaDataRoleAuthorizationStrategy.authorize(eventsContainer, RENDER, r);
         });
     }
 
-    private void updateEventsContainer(final AjaxRequestTarget target) {
-        setEvents();
+    protected void updateEventsContainer(final AjaxRequestTarget target) {
+        for (Iterator<EventCategory> itor = eventCategories.iterator();
+                itor.hasNext() && eventCategory.getEvents().isEmpty();) {
 
-        eventsContainer.addOrReplace(new EventSelectionPanel("eventsPanel", eventCategoryTO, model) {
+            EventCategory ec = itor.next();
+            if (eventCategory.getType() == ec.getType()
+                    && StringUtils.equals(eventCategory.getCategory(), ec.getCategory())
+                    && StringUtils.equals(eventCategory.getSubcategory(), ec.getSubcategory())) {
+
+                eventCategory.getEvents().addAll(ec.getEvents());
+            }
+        }
+
+        eventsContainer.addOrReplace(new EventSelectionPanel("eventsPanel", eventCategory, model) {
 
             private static final long serialVersionUID = 3513194801190026082L;
 
@@ -446,8 +452,4 @@ public abstract class EventCategoryPanel extends Panel {
         });
         target.add(eventsContainer);
     }
-
-    protected abstract List<String> getListAuthRoles();
-
-    protected abstract List<String> getChangeAuthRoles();
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/events/EventSelectionPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/events/EventSelectionPanel.java
index 6eb92fcf29..0ec214acee 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/events/EventSelectionPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/events/EventSelectionPanel.java
@@ -48,11 +48,11 @@ public abstract class EventSelectionPanel extends Panel {
 
     private final Set<String> selected = new HashSet<>();
 
-    public EventSelectionPanel(final String id, final EventCategory eventCategoryTO, final IModel<List<String>> model) {
+    public EventSelectionPanel(final String id, final EventCategory eventCategory, final IModel<List<String>> model) {
         super(id);
         setOutputMarkupId(true);
 
-        List<String> events = getEvents(eventCategoryTO);
+        List<String> events = getEvents(eventCategory);
 
         // needed to avoid model reset: model have to be managed into SelectedEventsPanel
         selected.addAll(model.getObject());
@@ -64,21 +64,21 @@ public abstract class EventSelectionPanel extends Panel {
 
             @Override
             protected void onUpdate(final AjaxRequestTarget target) {
-                Set<String> toBeRemoved = new HashSet<>();
-                Set<String> toBeAdded = new HashSet<>();
-
-                getEvents(eventCategoryTO).forEach(event -> {
-                    String eventString = AuditLoggerName.buildEvent(
-                            eventCategoryTO.getType(),
-                            eventCategoryTO.getCategory(),
-                            eventCategoryTO.getSubcategory(),
+                Set<AuditLoggerName> toBeRemoved = new HashSet<>();
+                Set<AuditLoggerName> toBeAdded = new HashSet<>();
+
+                getEvents(eventCategory).forEach(event -> {
+                    AuditLoggerName auditLoggerName = new AuditLoggerName(
+                            eventCategory.getType(),
+                            eventCategory.getCategory(),
+                            eventCategory.getSubcategory(),
                             event,
                             AuditElements.Result.SUCCESS);
 
-                    if (successGroup.getModelObject().contains(eventString)) {
-                        toBeAdded.add(eventString);
+                    if (successGroup.getModelObject().contains(auditLoggerName.toString())) {
+                        toBeAdded.add(auditLoggerName);
                     } else {
-                        toBeRemoved.add(eventString);
+                        toBeRemoved.add(auditLoggerName);
                     }
                 });
 
@@ -113,13 +113,13 @@ public abstract class EventSelectionPanel extends Panel {
             @Override
             protected void populateItem(final ListItem<String> item) {
                 item.add(new Check<>("successCheck",
-                    new Model<>(AuditLoggerName.buildEvent(
-                        eventCategoryTO.getType(),
-                        eventCategoryTO.getCategory(),
-                        eventCategoryTO.getSubcategory(),
-                        item.getModelObject(),
-                        AuditElements.Result.SUCCESS)),
-                    successGroup));
+                        new Model<>(AuditLoggerName.buildEvent(
+                                eventCategory.getType(),
+                                eventCategory.getCategory(),
+                                eventCategory.getSubcategory(),
+                                item.getModelObject(),
+                                AuditElements.Result.SUCCESS)),
+                        successGroup));
             }
         };
         successGroup.add(successView);
@@ -131,21 +131,21 @@ public abstract class EventSelectionPanel extends Panel {
 
             @Override
             protected void onUpdate(final AjaxRequestTarget target) {
-                Set<String> toBeRemoved = new HashSet<>();
-                Set<String> toBeAdded = new HashSet<>();
-
-                getEvents(eventCategoryTO).forEach(event -> {
-                    String eventString = AuditLoggerName.buildEvent(
-                            eventCategoryTO.getType(),
-                            eventCategoryTO.getCategory(),
-                            eventCategoryTO.getSubcategory(),
+                Set<AuditLoggerName> toBeRemoved = new HashSet<>();
+                Set<AuditLoggerName> toBeAdded = new HashSet<>();
+
+                getEvents(eventCategory).forEach(event -> {
+                    AuditLoggerName auditLoggerName = new AuditLoggerName(
+                            eventCategory.getType(),
+                            eventCategory.getCategory(),
+                            eventCategory.getSubcategory(),
                             event,
                             AuditElements.Result.FAILURE);
 
-                    if (failureGroup.getModelObject().contains(eventString)) {
-                        toBeAdded.add(eventString);
+                    if (failureGroup.getModelObject().contains(auditLoggerName.toString())) {
+                        toBeAdded.add(auditLoggerName);
                     } else {
-                        toBeRemoved.add(eventString);
+                        toBeRemoved.add(auditLoggerName);
                     }
                 });
 
@@ -169,13 +169,13 @@ public abstract class EventSelectionPanel extends Panel {
             @Override
             protected void populateItem(final ListItem<String> item) {
                 item.add(new Check<>("failureCheck",
-                    new Model<>(AuditLoggerName.buildEvent(
-                        eventCategoryTO.getType(),
-                        eventCategoryTO.getCategory(),
-                        eventCategoryTO.getSubcategory(),
-                        item.getModelObject(),
-                        AuditElements.Result.FAILURE)),
-                    failureGroup));
+                        new Model<>(AuditLoggerName.buildEvent(
+                                eventCategory.getType(),
+                                eventCategory.getCategory(),
+                                eventCategory.getSubcategory(),
+                                item.getModelObject(),
+                                AuditElements.Result.FAILURE)),
+                        failureGroup));
             }
         };
         failureGroup.add(failureView);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/events/SelectedEventsPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/events/SelectedEventsPanel.java
index b3c40091bc..cb04d68599 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/events/SelectedEventsPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/events/SelectedEventsPanel.java
@@ -18,9 +18,11 @@
  */
 package org.apache.syncope.client.console.events;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
 import org.apache.wicket.event.Broadcast;
@@ -51,18 +53,18 @@ public class SelectedEventsPanel extends Panel {
         add(selectionContainer);
 
         ListMultipleChoice<String> selectedEvents =
-            new ListMultipleChoice<>("selectedEvents", new ListModel<>(), model) {
-
-                private static final long serialVersionUID = 1226677544225737338L;
-
-                @Override
-                protected void onComponentTag(final ComponentTag tag) {
-                    super.onComponentTag(tag);
-                    tag.remove("size");
-                    tag.remove("multiple");
-                    tag.put("size", 5);
-                }
-            };
+                new ListMultipleChoice<>("selectedEvents", new ListModel<>(), model) {
+
+            private static final long serialVersionUID = 1226677544225737338L;
+
+            @Override
+            protected void onComponentTag(final ComponentTag tag) {
+                super.onComponentTag(tag);
+                tag.remove("size");
+                tag.remove("multiple");
+                tag.put("size", 5);
+            }
+        };
         selectedEvents.setMaxRows(5);
         selectedEvents.setChoiceRenderer(new IChoiceRenderer<>() {
 
@@ -103,12 +105,13 @@ public class SelectedEventsPanel extends Panel {
             EventSelectionChanged eventSelectionChanged = (EventSelectionChanged) event.getPayload();
 
             eventSelectionChanged.getToBeRemoved().
-                    forEach(toBeRemoved -> model.getObject().remove(toBeRemoved));
+                    forEach(toBeRemoved -> model.getObject().remove(toBeRemoved.toString()));
 
             eventSelectionChanged.getToBeAdded().stream().
-                    filter(toBeAdded -> !model.getObject().contains(toBeAdded)).
-                    forEach(toBeAdded -> model.getObject().add(toBeAdded));
+                    filter(toBeAdded -> !model.getObject().contains(toBeAdded.toString())).
+                    forEach(toBeAdded -> model.getObject().add(toBeAdded.toString()));
 
+            Collections.sort(model.getObject());
             eventSelectionChanged.getTarget().add(selectionContainer);
         }
     }
@@ -137,14 +140,15 @@ public class SelectedEventsPanel extends Panel {
 
         private final AjaxRequestTarget target;
 
-        private final Set<String> toBeRemoved;
+        private final Set<AuditLoggerName> toBeRemoved;
 
-        private final Set<String> toBeAdded;
+        private final Set<AuditLoggerName> toBeAdded;
 
         public EventSelectionChanged(
                 final AjaxRequestTarget target,
-                final Set<String> toBeAdded,
-                final Set<String> toBeRemoved) {
+                final Set<AuditLoggerName> toBeAdded,
+                final Set<AuditLoggerName> toBeRemoved) {
+
             this.target = target;
             this.toBeAdded = toBeAdded;
             this.toBeRemoved = toBeRemoved;
@@ -154,11 +158,11 @@ public class SelectedEventsPanel extends Panel {
             return target;
         }
 
-        public Set<String> getToBeRemoved() {
+        public Set<AuditLoggerName> getToBeRemoved() {
             return toBeRemoved;
         }
 
-        public Set<String> getToBeAdded() {
+        public Set<AuditLoggerName> getToBeAdded() {
             return toBeAdded;
         }
     }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java
index a3791f049b..367741f93b 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.stream.Collectors;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.commons.DirectoryDataProvider;
 import org.apache.syncope.client.console.commons.IdRepoConstants;
@@ -44,7 +45,9 @@ import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
 import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.audit.EventCategory;
 import org.apache.syncope.common.lib.to.NotificationTO;
+import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -53,6 +56,7 @@ import org.apache.wicket.event.Broadcast;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
 import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.StringResourceModel;
 import org.apache.wicket.spring.injection.annot.SpringBean;
@@ -77,6 +81,18 @@ public class NotificationDirectoryPanel
     @SpringBean
     protected SchemaRestClient schemaRestClient;
 
+    protected final IModel<List<EventCategory>> eventCategories = new LoadableDetachableModel<List<EventCategory>>() {
+
+        private static final long serialVersionUID = 4659376149825914247L;
+
+        @Override
+        protected List<EventCategory> load() {
+            // cannot notify about WA events
+            return auditRestClient.listEvents().stream().
+                    filter(c -> c.getType() != AuditElements.EventCategoryType.WA).collect(Collectors.toList());
+        }
+    };
+
     protected final BaseModal<String> utilityModal = new BaseModal<>(Constants.OUTER);
 
     public NotificationDirectoryPanel(
@@ -97,10 +113,10 @@ public class NotificationDirectoryPanel
         addNewItemPanelBuilder(new NotificationWizardBuilder(
                 new NotificationTO(),
                 notificationRestClient,
-                auditRestClient,
                 anyTypeRestClient,
                 implementationRestClient,
                 schemaRestClient,
+                eventCategories,
                 pageRef), true);
 
         initResultTable();
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java
index d506b3bfe2..c4f0a8d4dd 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java
@@ -33,7 +33,6 @@ import org.apache.syncope.client.console.panels.search.GroupSearchPanel;
 import org.apache.syncope.client.console.panels.search.SearchClause;
 import org.apache.syncope.client.console.panels.search.UserSearchPanel;
 import org.apache.syncope.client.console.rest.AnyTypeRestClient;
-import org.apache.syncope.client.console.rest.AuditRestClient;
 import org.apache.syncope.client.console.rest.ImplementationRestClient;
 import org.apache.syncope.client.console.rest.NotificationRestClient;
 import org.apache.syncope.client.console.rest.SchemaRestClient;
@@ -45,6 +44,7 @@ import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
 import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.audit.EventCategory;
 import org.apache.syncope.common.lib.to.AnyTypeTO;
 import org.apache.syncope.common.lib.to.DerSchemaTO;
 import org.apache.syncope.common.lib.to.ImplementationTO;
@@ -78,30 +78,30 @@ public class NotificationWizardBuilder extends BaseAjaxWizardBuilder<Notificatio
 
     protected final NotificationRestClient notificationRestClient;
 
-    protected final AuditRestClient auditRestClient;
-
     protected final AnyTypeRestClient anyTypeRestClient;
 
     protected final ImplementationRestClient implementationRestClient;
 
     protected final SchemaRestClient schemaRestClient;
 
+    protected final IModel<List<EventCategory>> eventCategories;
+
     public NotificationWizardBuilder(
             final NotificationTO notificationTO,
             final NotificationRestClient notificationRestClient,
-            final AuditRestClient auditRestClient,
             final AnyTypeRestClient anyTypeRestClient,
             final ImplementationRestClient implementationRestClient,
             final SchemaRestClient schemaRestClient,
+            final IModel<List<EventCategory>> eventCategories,
             final PageReference pageRef) {
 
         super(new NotificationWrapper(notificationTO), pageRef);
 
         this.notificationRestClient = notificationRestClient;
-        this.auditRestClient = auditRestClient;
         this.anyTypeRestClient = anyTypeRestClient;
         this.implementationRestClient = implementationRestClient;
         this.schemaRestClient = schemaRestClient;
+        this.eventCategories = eventCategories;
     }
 
     @Override
@@ -182,7 +182,7 @@ public class NotificationWizardBuilder extends BaseAjaxWizardBuilder<Notificatio
 
             add(new EventCategoryPanel(
                     "eventSelection",
-                    auditRestClient.listEvents(),
+                    eventCategories.getObject(),
                     new PropertyModel<>(modelObject.getInnerObject(), "events")) {
 
                 private static final long serialVersionUID = 6429053774964787735L;
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Audit.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Audit.java
index fe1ae591b2..6a824f8724 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Audit.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Audit.java
@@ -21,19 +21,17 @@ package org.apache.syncope.client.console.pages;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
 import org.apache.syncope.client.console.events.EventCategoryPanel;
 import org.apache.syncope.client.console.events.SelectedEventsPanel;
 import org.apache.syncope.client.console.rest.AuditRestClient;
 import org.apache.syncope.common.lib.audit.EventCategory;
-import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.AuditLoggerName;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.wicket.event.IEvent;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.model.util.ListModel;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
@@ -46,8 +44,7 @@ public class Audit extends BasePage {
     @SpringBean
     protected AuditRestClient auditRestClient;
 
-    protected final LoadableDetachableModel<List<EventCategory>> eventCategories =
-            new LoadableDetachableModel<List<EventCategory>>() {
+    protected final IModel<List<EventCategory>> eventCategories = new LoadableDetachableModel<List<EventCategory>>() {
 
         private static final long serialVersionUID = 4659376149825914247L;
 
@@ -105,35 +102,9 @@ public class Audit extends BasePage {
                     SelectedEventsPanel.EventSelectionChanged eventSelectionChanged =
                             (SelectedEventsPanel.EventSelectionChanged) event.getPayload();
 
-                    eventSelectionChanged.getToBeRemoved().forEach(toBeRemoved -> {
-                        Pair<EventCategory, AuditElements.Result> eventCategory =
-                                AuditLoggerName.parseEventCategory(toBeRemoved);
-
-                        AuditLoggerName auditLoggerName = new AuditLoggerName(
-                                eventCategory.getKey().getType(),
-                                eventCategory.getKey().getCategory(),
-                                eventCategory.getKey().getSubcategory(),
-                                CollectionUtils.isEmpty(eventCategory.getKey().getEvents())
-                                ? null : eventCategory.getKey().getEvents().iterator().next(),
-                                eventCategory.getValue());
-
-                        auditRestClient.disableAudit(auditLoggerName);
-                    });
-
-                    eventSelectionChanged.getToBeAdded().forEach(toBeAdded -> {
-                        Pair<EventCategory, AuditElements.Result> eventCategory =
-                                AuditLoggerName.parseEventCategory(toBeAdded);
-
-                        AuditLoggerName auditLoggerName = new AuditLoggerName(
-                                eventCategory.getKey().getType(),
-                                eventCategory.getKey().getCategory(),
-                                eventCategory.getKey().getSubcategory(),
-                                CollectionUtils.isEmpty(eventCategory.getKey().getEvents())
-                                ? null : eventCategory.getKey().getEvents().iterator().next(),
-                                eventCategory.getValue());
-
-                        auditRestClient.enableAudit(auditLoggerName);
-                    });
+                    eventSelectionChanged.getToBeRemoved().forEach(auditRestClient::delete);
+
+                    eventSelectionChanged.getToBeAdded().forEach(auditRestClient::enable);
                 }
             }
         });
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/AuditRestClient.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/AuditRestClient.java
index 92f81033e2..3a5a5d5c8e 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/AuditRestClient.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/AuditRestClient.java
@@ -50,14 +50,14 @@ public class AuditRestClient extends BaseRestClient {
                 collect(Collectors.toList());
     }
 
-    public void enableAudit(final AuditLoggerName auditLoggerName) {
+    public void enable(final AuditLoggerName auditLoggerName) {
         AuditConfTO audit = new AuditConfTO();
         audit.setKey(auditLoggerName.toAuditKey());
         audit.setActive(true);
         getService(AuditService.class).set(audit);
     }
 
-    public void disableAudit(final AuditLoggerName auditLoggerName) {
+    public void delete(final AuditLoggerName auditLoggerName) {
         try {
             getService(AuditService.class).delete(auditLoggerName.toAuditKey());
         } catch (SyncopeClientException e) {
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java
index 5750fcd9dd..3e978b5393 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java
@@ -20,7 +20,6 @@ package org.apache.syncope.common.lib.types;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
-import java.text.ParseException;
 import java.util.Map;
 import java.util.Optional;
 import org.apache.commons.lang3.StringUtils;
@@ -46,97 +45,13 @@ public class AuditLoggerName implements BaseBean {
         return domain + '.' + loggerName;
     }
 
-    private final EventCategoryType type;
-
-    private final String category;
-
-    private final String subcategory;
-
-    private final String event;
-
-    private final Result result;
-
-    @JsonCreator
-    public AuditLoggerName(
-            @JsonProperty("type") final AuditElements.EventCategoryType type,
-            @JsonProperty("category") final String category,
-            @JsonProperty("subcategory") final String subcategory,
-            @JsonProperty("event") final String event,
-            @JsonProperty("result") final Result result) {
-
-        super();
-
-        this.type = Optional.ofNullable(type).orElse(EventCategoryType.CUSTOM);
-        this.category = category;
-        this.subcategory = subcategory;
-        this.event = event;
-        this.result = Optional.ofNullable(result).orElse(Result.SUCCESS);
-    }
-
-    public AuditElements.EventCategoryType getType() {
-        return type;
-    }
-
-    public String getEvent() {
-        return event;
-    }
-
-    public String getCategory() {
-        return category;
-    }
-
-    public Result getResult() {
-        return result;
-    }
-
-    public String getSubcategory() {
-        return subcategory;
-    }
-
-    public String toAuditKey() {
-        return new StringBuilder().append(AUDIT_PREFIX).append('.').
-                append(buildEvent(type, category, subcategory, event, result)).toString();
-    }
-
-    @Override
-    public int hashCode() {
-        return new HashCodeBuilder().
-                append(type).
-                append(category).
-                append(subcategory).
-                append(event).
-                append(result).
-                build();
-    }
-
-    @Override
-    public boolean equals(final Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final AuditLoggerName other = (AuditLoggerName) obj;
-        return new EqualsBuilder().
-                append(type, other.type).
-                append(category, other.category).
-                append(subcategory, other.subcategory).
-                append(event, other.event).
-                append(result, other.result).
-                build();
-    }
-
-    public static AuditLoggerName fromAuditKey(final String key) throws ParseException {
+    public static AuditLoggerName fromAuditKey(final String key) {
         if (StringUtils.isBlank(key)) {
             throw new IllegalArgumentException("Null value not permitted");
         }
 
         if (!key.startsWith(AUDIT_PREFIX)) {
-            throw new ParseException("Audit logger name must start with " + AUDIT_PREFIX, 0);
+            throw new IllegalArgumentException("Audit logger name must start with " + AUDIT_PREFIX);
         }
 
         Map.Entry<EventCategory, Result> eventCategory = parseEventCategory(key.replace(AUDIT_PREFIX + '.', ""));
@@ -163,23 +78,7 @@ public class AuditLoggerName implements BaseBean {
                 condition = Result.SUCCESS;
                 eventCategory.getEvents().add(event);
             } else {
-                EventCategoryType type;
-
-                if (EventCategoryType.PROPAGATION.name().equals(elements[0])) {
-                    type = EventCategoryType.PROPAGATION;
-                } else if (EventCategoryType.PULL.name().equals(elements[0])) {
-                    type = EventCategoryType.PULL;
-                } else if (EventCategoryType.PUSH.name().equals(elements[0])) {
-                    type = EventCategoryType.PUSH;
-                } else {
-                    try {
-                        type = EventCategoryType.valueOf(elements[0]);
-                    } catch (Exception e) {
-                        type = EventCategoryType.CUSTOM;
-                    }
-                }
-
-                eventCategory.setType(type);
+                eventCategory.setType(EventCategoryType.valueOf(elements[0]));
 
                 eventCategory.setCategory(StringUtils.isNotEmpty(elements[1]) ? elements[1] : null);
 
@@ -205,7 +104,7 @@ public class AuditLoggerName implements BaseBean {
      * @param category event category.
      * @param subcategory event subcategory.
      * @param event event.
-     * @param condition result value condition.
+     * @param result result value condition.
      * @return event string.
      */
     public static String buildEvent(
@@ -213,7 +112,7 @@ public class AuditLoggerName implements BaseBean {
             final String category,
             final String subcategory,
             final String event,
-            final AuditElements.Result condition) {
+            final AuditElements.Result result) {
 
         StringBuilder eventBuilder = new StringBuilder();
 
@@ -235,12 +134,100 @@ public class AuditLoggerName implements BaseBean {
         }
         eventBuilder.append(']');
 
-        if (condition != null) {
+        if (result != null) {
             eventBuilder.append(":[").
-                    append(condition).
+                    append(result).
                     append(']');
         }
 
         return eventBuilder.toString();
     }
+
+    private final EventCategoryType type;
+
+    private final String category;
+
+    private final String subcategory;
+
+    private final String event;
+
+    private final Result result;
+
+    @JsonCreator
+    public AuditLoggerName(
+            @JsonProperty("type") final AuditElements.EventCategoryType type,
+            @JsonProperty("category") final String category,
+            @JsonProperty("subcategory") final String subcategory,
+            @JsonProperty("event") final String event,
+            @JsonProperty("result") final Result result) {
+
+        super();
+
+        this.type = Optional.ofNullable(type).orElse(EventCategoryType.CUSTOM);
+        this.category = category;
+        this.subcategory = subcategory;
+        this.event = event;
+        this.result = Optional.ofNullable(result).orElse(Result.SUCCESS);
+    }
+
+    public AuditElements.EventCategoryType getType() {
+        return type;
+    }
+
+    public String getEvent() {
+        return event;
+    }
+
+    public String getCategory() {
+        return category;
+    }
+
+    public Result getResult() {
+        return result;
+    }
+
+    public String getSubcategory() {
+        return subcategory;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().
+                append(type).
+                append(category).
+                append(subcategory).
+                append(event).
+                append(result).
+                build();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final AuditLoggerName other = (AuditLoggerName) obj;
+        return new EqualsBuilder().
+                append(type, other.type).
+                append(category, other.category).
+                append(subcategory, other.subcategory).
+                append(event, other.event).
+                append(result, other.result).
+                build();
+    }
+
+    @Override
+    public String toString() {
+        return buildEvent(type, category, subcategory, event, result);
+    }
+
+    public String toAuditKey() {
+        return AUDIT_PREFIX + "." + toString();
+    }
 }
diff --git a/common/idrepo/lib/src/test/java/org/apache/syncope/common/lib/types/AuditLoggerNameTest.java b/common/idrepo/lib/src/test/java/org/apache/syncope/common/lib/types/AuditLoggerNameTest.java
new file mode 100644
index 0000000000..582068c794
--- /dev/null
+++ b/common/idrepo/lib/src/test/java/org/apache/syncope/common/lib/types/AuditLoggerNameTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.common.lib.types;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class AuditLoggerNameTest {
+
+    @Test
+    void custom() {
+        AuditLoggerName aln = new AuditLoggerName(
+                AuditElements.EventCategoryType.CUSTOM, null, null, "MY_EVENT", AuditElements.Result.SUCCESS);
+        assertEquals("syncope.audit.[CUSTOM]:[]:[]:[MY_EVENT]:[SUCCESS]", aln.toAuditKey());
+        assertEquals("[CUSTOM]:[]:[]:[MY_EVENT]:[SUCCESS]", aln.toString());
+
+        assertEquals(aln, AuditLoggerName.fromAuditKey(aln.toAuditKey()));
+    }
+}
diff --git a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/IdMLogicTestContext.java b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/IdMLogicTestContext.java
index d7855a3941..e010822542 100644
--- a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/IdMLogicTestContext.java
+++ b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/IdMLogicTestContext.java
@@ -33,6 +33,7 @@ import org.apache.syncope.core.provisioning.api.ImplementationLookup;
 import org.apache.syncope.core.provisioning.java.ProvisioningContext;
 import org.apache.syncope.core.spring.security.SecurityContext;
 import org.apache.syncope.core.workflow.java.WorkflowContext;
+import org.springframework.boot.logging.LoggingSystem;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -61,6 +62,11 @@ public class IdMLogicTestContext {
         return mock(JavaMailSender.class);
     }
 
+    @Bean
+    public LoggingSystem loggingSystem() {
+        return mock(LoggingSystem.class);
+    }
+
     @Bean
     public ImplementationLookup implementationLookup() {
         return new DummyImplementationLookup();
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
index 267f19f575..296a44e22a 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
@@ -29,7 +29,6 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.tuple.Pair;
-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.config.LoggerConfig;
@@ -59,6 +58,8 @@ import org.apache.syncope.core.provisioning.api.data.AuditDataBinder;
 import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate;
 import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.springframework.boot.logging.LogLevel;
+import org.springframework.boot.logging.LoggingSystem;
 import org.springframework.core.io.Resource;
 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
 import org.springframework.core.io.support.ResourcePatternResolver;
@@ -86,13 +87,16 @@ public class AuditLogic extends AbstractTransactionalLogic<AuditConfTO> {
 
     protected final List<AuditAppender> auditAppenders;
 
+    protected final LoggingSystem loggingSystem;
+
     public AuditLogic(
             final AuditConfDAO auditConfDAO,
             final ExternalResourceDAO resourceDAO,
             final EntityFactory entityFactory,
             final AuditDataBinder binder,
             final AuditManager auditManager,
-            final List<AuditAppender> auditAppenders) {
+            final List<AuditAppender> auditAppenders,
+            final LoggingSystem loggingSystem) {
 
         this.auditConfDAO = auditConfDAO;
         this.resourceDAO = resourceDAO;
@@ -100,6 +104,7 @@ public class AuditLogic extends AbstractTransactionalLogic<AuditConfTO> {
         this.binder = binder;
         this.auditManager = auditManager;
         this.auditAppenders = auditAppenders;
+        this.loggingSystem = loggingSystem;
     }
 
     @PreAuthorize("hasRole('" + IdRepoEntitlement.AUDIT_LIST + "')")
@@ -126,7 +131,7 @@ public class AuditLogic extends AbstractTransactionalLogic<AuditConfTO> {
         audit.setActive(auditTO.isActive());
         audit = auditConfDAO.save(audit);
 
-        setLevel(audit.getKey(), audit.isActive() ? Level.DEBUG : Level.OFF);
+        setLevel(audit.getKey(), audit.isActive() ? LogLevel.DEBUG : LogLevel.OFF);
     }
 
     @PreAuthorize("hasRole('" + IdRepoEntitlement.AUDIT_DELETE + "')")
@@ -135,23 +140,21 @@ public class AuditLogic extends AbstractTransactionalLogic<AuditConfTO> {
                 orElseThrow(() -> new NotFoundException("Audit " + key));
         auditConfDAO.delete(audit);
 
-        setLevel(audit.getKey(), Level.OFF);
+        setLevel(audit.getKey(), LogLevel.OFF);
     }
 
-    protected void setLevel(final String key, final Level level) {
+    protected void setLevel(final String key, final LogLevel level) {
         String auditLoggerName = AuditLoggerName.getAuditEventLoggerName(AuthContextUtils.getDomain(), key);
 
         LoggerContext logCtx = (LoggerContext) LogManager.getContext(false);
         LoggerConfig logConf = logCtx.getConfiguration().getLoggerConfig(auditLoggerName);
 
-        // SYNCOPE-1144 For each custom audit appender class add related appenders to log4j logger
         auditAppenders.stream().
                 filter(appender -> appender.getEvents().stream().
                 anyMatch(event -> key.equalsIgnoreCase(event.toAuditKey()))).
                 forEach(auditAppender -> AuditLoader.addAppenderToLoggerContext(logCtx, auditAppender, logConf));
 
-        logConf.setLevel(level);
-        logCtx.updateLoggers();
+        loggingSystem.setLogLevel(auditLoggerName, level);
     }
 
     @PreAuthorize("hasRole('" + IdRepoEntitlement.AUDIT_LIST + "') "
@@ -163,16 +166,35 @@ public class AuditLogic extends AbstractTransactionalLogic<AuditConfTO> {
             }
         }
 
-        // use set to avoid duplications or null elements
         Set<EventCategory> events = new HashSet<>();
 
+        EventCategory authenticationEventCategory = new EventCategory();
+        authenticationEventCategory.setCategory(AuditElements.AUTHENTICATION_CATEGORY);
+        authenticationEventCategory.getEvents().add(AuditElements.LOGIN_EVENT);
+        events.add(authenticationEventCategory);
+
+        events.add(new EventCategory(EventCategoryType.PROPAGATION));
+        events.add(new EventCategory(EventCategoryType.PULL));
+        events.add(new EventCategory(EventCategoryType.PUSH));
+
+        EventCategory pullTaskEventCategory = new EventCategory(EventCategoryType.TASK);
+        pullTaskEventCategory.setCategory(PullJobDelegate.class.getSimpleName());
+        events.add(pullTaskEventCategory);
+
+        EventCategory pushTaskEventCategory = new EventCategory(EventCategoryType.TASK);
+        pushTaskEventCategory.setCategory(PushJobDelegate.class.getSimpleName());
+        events.add(pushTaskEventCategory);
+
+        events.add(new EventCategory(EventCategoryType.WA));
+        events.add(new EventCategory(EventCategoryType.CUSTOM));
+
         try {
             ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
             MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
 
             String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                     + ClassUtils.convertClassNameToResourcePath(
-                            SystemPropertyUtils.resolvePlaceholders(this.getClass().getPackage().getName()))
+                            SystemPropertyUtils.resolvePlaceholders(getClass().getPackage().getName()))
                     + "/**/*.class";
 
             Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
@@ -197,16 +219,6 @@ public class AuditLogic extends AbstractTransactionalLogic<AuditConfTO> {
                 }
             }
 
-            // SYNCOPE-608
-            EventCategory authenticationControllerEvents = new EventCategory();
-            authenticationControllerEvents.setCategory(AuditElements.AUTHENTICATION_CATEGORY);
-            authenticationControllerEvents.getEvents().add(AuditElements.LOGIN_EVENT);
-            events.add(authenticationControllerEvents);
-
-            events.add(new EventCategory(EventCategoryType.PROPAGATION));
-            events.add(new EventCategory(EventCategoryType.PULL));
-            events.add(new EventCategory(EventCategoryType.PUSH));
-
             for (AnyTypeKind anyTypeKind : AnyTypeKind.values()) {
                 resourceDAO.findAll().forEach(resource -> {
                     EventCategory propEventCategory = new EventCategory(EventCategoryType.PROPAGATION);
@@ -243,14 +255,6 @@ public class AuditLogic extends AbstractTransactionalLogic<AuditConfTO> {
                     events.add(pushEventCategory);
                 });
             }
-
-            EventCategory eventCategory = new EventCategory(EventCategoryType.TASK);
-            eventCategory.setCategory(PullJobDelegate.class.getSimpleName());
-            events.add(eventCategory);
-
-            eventCategory = new EventCategory(EventCategoryType.TASK);
-            eventCategory.setCategory(PushJobDelegate.class.getSimpleName());
-            events.add(eventCategory);
         } catch (Exception e) {
             LOG.error("Failure retrieving audit/notification events", e);
         }
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
index b4d3ae3c4d..0c90c765b6 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
@@ -107,6 +107,7 @@ import org.apache.syncope.core.provisioning.api.rules.RuleEnforcer;
 import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
 import org.apache.syncope.core.spring.security.SecurityProperties;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.logging.LoggingSystem;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
@@ -133,8 +134,8 @@ public class IdRepoLogicContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public AuditAccessor auditAccessor(final AuditConfDAO auditConfDAO) {
-        return new AuditAccessor(auditConfDAO);
+    public AuditAccessor auditAccessor(final AuditConfDAO auditConfDAO, final LoggingSystem loggingSystem) {
+        return new AuditAccessor(auditConfDAO, loggingSystem);
     }
 
     @ConditionalOnMissingBean
@@ -251,7 +252,8 @@ public class IdRepoLogicContext {
             final EntityFactory entityFactory,
             final AuditDataBinder binder,
             final AuditManager auditManager,
-            final List<AuditAppender> auditAppenders) {
+            final List<AuditAppender> auditAppenders,
+            final LoggingSystem loggingSystem) {
 
         return new AuditLogic(
                 auditConfDAO,
@@ -259,7 +261,8 @@ public class IdRepoLogicContext {
                 entityFactory,
                 binder,
                 auditManager,
-                auditAppenders);
+                auditAppenders,
+                loggingSystem);
     }
 
     @ConditionalOnMissingBean
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/AuditAccessor.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/AuditAccessor.java
index 3aed0ccca6..5393915b16 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/AuditAccessor.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/AuditAccessor.java
@@ -18,16 +18,11 @@
  */
 package org.apache.syncope.core.logic.init;
 
-import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.config.LoggerConfig;
 import org.apache.syncope.common.lib.types.AuditLoggerName;
 import org.apache.syncope.core.persistence.api.dao.AuditConfDAO;
-import org.apache.syncope.core.persistence.api.entity.AuditConf;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.springframework.boot.logging.LogLevel;
+import org.springframework.boot.logging.LoggingSystem;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
@@ -39,22 +34,17 @@ public class AuditAccessor {
 
     protected final AuditConfDAO auditConfDAO;
 
-    public AuditAccessor(final AuditConfDAO auditConfDAO) {
+    protected final LoggingSystem loggingSystem;
+
+    public AuditAccessor(final AuditConfDAO auditConfDAO, final LoggingSystem loggingSystem) {
         this.auditConfDAO = auditConfDAO;
+        this.loggingSystem = loggingSystem;
     }
 
     @Transactional
-    public void synchronizeLoggingWithAudit(final LoggerContext ctx) {
-        Map<String, AuditConf> audits = auditConfDAO.findAll().stream().
-                collect(Collectors.toMap(
-                        audit -> AuditLoggerName.getAuditEventLoggerName(AuthContextUtils.getDomain(), audit.getKey()),
-                        Function.identity()));
-
-        audits.forEach((logger, audit) -> {
-            LoggerConfig logConf = ctx.getConfiguration().getLoggerConfig(logger);
-            logConf.setLevel(audit.isActive() ? Level.DEBUG : Level.OFF);
-        });
-
-        ctx.updateLoggers();
+    public void synchronizeLoggingWithAudit() {
+        auditConfDAO.findAll().forEach(auditConf -> loggingSystem.setLogLevel(
+                AuditLoggerName.getAuditEventLoggerName(AuthContextUtils.getDomain(), auditConf.getKey()),
+                auditConf.isActive() ? LogLevel.DEBUG : LogLevel.OFF));
     }
 }
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/AuditLoader.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/AuditLoader.java
index 828f272201..c43eb12ae6 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/AuditLoader.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/AuditLoader.java
@@ -25,7 +25,6 @@ import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.core.Appender;
 import org.apache.logging.log4j.core.LoggerContext;
-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.audit.AuditAppender;
@@ -41,20 +40,20 @@ public class AuditLoader implements SyncopeCoreLoader {
             final AuditAppender auditAppender,
             final LoggerConfig eventLogConf) {
 
-        Appender targetAppender = ctx.getConfiguration().getAppender(auditAppender.getTargetAppenderName());
-        if (targetAppender == null) {
-            targetAppender = auditAppender.getTargetAppender();
-        }
+        Appender targetAppender = Optional.ofNullable(
+                ctx.getConfiguration().getAppender(auditAppender.getTargetAppenderName())).map(Appender.class::cast).
+                orElseGet(() -> auditAppender.getTargetAppender());
         targetAppender.start();
         ctx.getConfiguration().addAppender(targetAppender);
 
-        Optional<RewriteAppender> rewriteAppender = auditAppender.getRewriteAppender();
-        if (rewriteAppender.isPresent()) {
-            rewriteAppender.get().start();
-            eventLogConf.addAppender(rewriteAppender.get(), Level.DEBUG, null);
-        } else {
-            eventLogConf.addAppender(targetAppender, Level.DEBUG, null);
-        }
+        auditAppender.getRewriteAppender().ifPresentOrElse(
+                rewriteAppender -> {
+                    rewriteAppender.start();
+                    eventLogConf.addAppender(rewriteAppender, Level.DEBUG, null);
+                },
+                () -> eventLogConf.addAppender(targetAppender, Level.DEBUG, null));
+
+        ctx.updateLoggers();
     }
 
     protected final AuditAccessor auditAccessor;
@@ -68,14 +67,13 @@ public class AuditLoader implements SyncopeCoreLoader {
 
     @Override
     public int getOrder() {
-        return 300;
+        return 500;
     }
 
     @Override
     public void load(final String domain, final DataSource datasource) {
         LoggerContext logCtx = (LoggerContext) LogManager.getContext(false);
 
-        // SYNCOPE-1144 For each custom audit appender class add related appenders to log4j logger
         auditAppenders.forEach(auditAppender -> auditAppender.getEvents().stream().
                 map(event -> AuditLoggerName.getAuditEventLoggerName(domain, event.toAuditKey())).
                 forEach(domainAuditLoggerName -> {
@@ -85,9 +83,9 @@ public class AuditLoader implements SyncopeCoreLoader {
                     if (isRootLogConf) {
                         eventLogConf = new LoggerConfig(domainAuditLoggerName, null, false);
                     }
+                    eventLogConf.setLevel(Level.DEBUG);
 
                     addAppenderToLoggerContext(logCtx, auditAppender, eventLogConf);
-                    eventLogConf.setLevel(Level.DEBUG);
 
                     if (isRootLogConf) {
                         logCtx.getConfiguration().addLogger(domainAuditLoggerName, eventLogConf);
@@ -95,10 +93,8 @@ public class AuditLoader implements SyncopeCoreLoader {
                 }));
 
         AuthContextUtils.callAsAdmin(domain, () -> {
-            auditAccessor.synchronizeLoggingWithAudit(logCtx);
+            auditAccessor.synchronizeLoggingWithAudit();
             return null;
         });
-
-        logCtx.updateLoggers();
     }
 }
diff --git a/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/IdRepoLogicTestContext.java b/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/IdRepoLogicTestContext.java
index 9a1aead870..e764e074cd 100644
--- a/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/IdRepoLogicTestContext.java
+++ b/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/IdRepoLogicTestContext.java
@@ -33,6 +33,7 @@ import org.apache.syncope.core.provisioning.api.ImplementationLookup;
 import org.apache.syncope.core.provisioning.java.ProvisioningContext;
 import org.apache.syncope.core.spring.security.SecurityContext;
 import org.apache.syncope.core.workflow.java.WorkflowContext;
+import org.springframework.boot.logging.LoggingSystem;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -61,6 +62,11 @@ public class IdRepoLogicTestContext {
         return mock(JavaMailSender.class);
     }
 
+    @Bean
+    public LoggingSystem loggingSystem() {
+        return mock(LoggingSystem.class);
+    }
+
     @Bean
     public ImplementationLookup implementationLookup() {
         return new DummyImplementationLookup();
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
index 509ab4c9f6..8da5d1e231 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
@@ -155,8 +155,8 @@ public class LDAPMembershipPullActions implements PullActions {
 
         groupDAO.findUMemberships(groupDAO.find(entity.getKey())).forEach(uMembership -> {
             Set<String> memb = membershipsBefore.computeIfAbsent(
-                uMembership.getLeftEnd().getKey(),
-                k -> Collections.synchronizedSet(new HashSet<>()));
+                    uMembership.getLeftEnd().getKey(),
+                    k -> Collections.synchronizedSet(new HashSet<>()));
             memb.add(entity.getKey());
         });
     }
@@ -192,8 +192,8 @@ public class LDAPMembershipPullActions implements PullActions {
                     profile.getConnector());
             if (match.isPresent()) {
                 Set<String> memb = membershipsAfter.computeIfAbsent(
-                    match.get().getAny().getKey(),
-                    k -> Collections.synchronizedSet(new HashSet<>()));
+                        match.get().getAny().getKey(),
+                        k -> Collections.synchronizedSet(new HashSet<>()));
                 memb.add(entity.getKey());
             } else {
                 LOG.warn("Could not find matching user for {}", membValue);
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java
index f89210118a..1b1462b274 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java
@@ -28,7 +28,6 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -59,7 +58,6 @@ import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
 import org.apache.syncope.core.provisioning.api.rules.PullMatch;
 import org.apache.syncope.core.provisioning.java.AbstractTest;
-import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.ConnectorObject;
 import org.identityconnectors.framework.common.objects.SyncDelta;
 import org.identityconnectors.framework.common.objects.Uid;
@@ -97,8 +95,6 @@ public class LDAPMembershipPullActionsTest extends AbstractTest {
     @Mock
     private ProvisioningReport result;
 
-    private Map<String, Set<String>> membershipsAfter;
-
     @Mock
     private ProvisioningTask<?> pullTask;
 
@@ -121,6 +117,8 @@ public class LDAPMembershipPullActionsTest extends AbstractTest {
 
     private AnyUR anyReq;
 
+    private Map<String, Set<String>> membershipsAfter;
+
     private Map<String, Set<String>> membershipsBefore;
 
     private User user;
@@ -130,25 +128,25 @@ public class LDAPMembershipPullActionsTest extends AbstractTest {
     @BeforeEach
     public void initTest() {
         user = entityFactory.newEntity(User.class);
+        ReflectionTestUtils.setField(user, "id", UUID.randomUUID().toString());
+
         UMembership uMembership = entityFactory.newEntity(UMembership.class);
         uMembership.setLeftEnd(user);
-        ReflectionTestUtils.setField(user, "id", UUID.randomUUID().toString());
-        List<UMembership> uMembList = List.of(uMembership);
 
         anyReq = new UserUR();
+
         membershipsBefore = new HashMap<>();
-        membershipsAfter = new HashMap<>();
         ReflectionTestUtils.setField(ldapMembershipPullActions, "membershipsBefore", membershipsBefore);
+        membershipsAfter = new HashMap<>();
         ReflectionTestUtils.setField(ldapMembershipPullActions, "membershipsAfter", membershipsAfter);
 
-        lenient().when(groupDAO.findUMemberships(groupDAO.find(anyString()))).thenReturn(uMembList);
+        lenient().when(groupDAO.findUMemberships(groupDAO.find(anyString()))).thenReturn(List.of(uMembership));
 
         ConnConfPropSchema connConfPropSchema = new ConnConfPropSchema();
         connConfPropSchema.setName("testSchemaName");
         ConnConfProperty connConfProperty = new ConnConfProperty();
         connConfProperty.setSchema(connConfPropSchema);
-        connConfProperties = new HashSet<>();
-        connConfProperties.add(connConfProperty);
+        connConfProperties = Set.of(connConfProperty);
 
         lenient().when(profile.getTask()).thenAnswer(ic -> pullTask);
         lenient().when(pullTask.getResource()).thenReturn(resource);
@@ -170,23 +168,20 @@ public class LDAPMembershipPullActionsTest extends AbstractTest {
     public void beforeUpdateWithGroupTOAndEmptyMemberships() throws JobExecutionException {
         entity = new GroupTO();
         entity.setKey(UUID.randomUUID().toString());
-        Set<String> expected = new HashSet<>();
-        expected.add(entity.getKey());
 
         ldapMembershipPullActions.beforeUpdate(profile, syncDelta, entity, anyReq);
 
         assertTrue(entity instanceof GroupTO);
         assertEquals(1, membershipsBefore.get(user.getKey()).size());
-        assertEquals(expected, membershipsBefore.get(user.getKey()));
+        assertEquals(Set.of(entity.getKey()), membershipsBefore.get(user.getKey()));
     }
 
     @Test
     public void beforeUpdate() throws JobExecutionException {
         entity = new UserTO();
         entity.setKey(UUID.randomUUID().toString());
-        Set<String> memb = new HashSet<>();
-        memb.add(entity.getKey());
-        membershipsBefore.put(user.getKey(), memb);
+
+        membershipsBefore.put(user.getKey(), Set.of(entity.getKey()));
 
         ldapMembershipPullActions.beforeUpdate(profile, syncDelta, entity, anyReq);
 
@@ -197,17 +192,15 @@ public class LDAPMembershipPullActionsTest extends AbstractTest {
     @Test
     public void after() throws JobExecutionException {
         entity = new GroupTO();
-        String expectedUid = UUID.randomUUID().toString();
-        Attribute attribute = new Uid(expectedUid);
-        List<String> expected = List.of(expectedUid);
+        entity.setKey(UUID.randomUUID().toString());
 
-        when(connectorObj.getAttributeByName(anyString())).thenReturn(attribute);
+        when(connectorObj.getAttributeByName(anyString())).thenReturn(new Uid(UUID.randomUUID().toString()));
         when(inboundMatcher.match(any(AnyType.class), anyString(), any(ExternalResource.class), any(Connector.class))).
                 thenReturn(Optional.of(new PullMatch(MatchType.ANY, user)));
 
         ldapMembershipPullActions.after(profile, syncDelta, entity, result);
 
         assertEquals(1, membershipsAfter.get(user.getKey()).size());
-        assertEquals(expected, attribute.getValue());
+        assertEquals(entity.getKey(), membershipsAfter.get(user.getKey()).iterator().next());
     }
 }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
index 25b83886b7..a3bcd2040d 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
@@ -44,12 +44,16 @@ import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
+import javax.ws.rs.core.Response;
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.SerializationUtils;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.audit.AuditEntry;
 import org.apache.syncope.common.lib.audit.EventCategory;
+import org.apache.syncope.common.lib.request.AnyObjectUR;
 import org.apache.syncope.common.lib.request.AttrPatch;
 import org.apache.syncope.common.lib.request.ResourceDR;
 import org.apache.syncope.common.lib.request.UserUR;
@@ -58,9 +62,11 @@ import org.apache.syncope.common.lib.to.AuditConfTO;
 import org.apache.syncope.common.lib.to.ConnInstanceTO;
 import org.apache.syncope.common.lib.to.ConnPoolConfTO;
 import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.to.ImplementationTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.to.PushTaskTO;
+import org.apache.syncope.common.lib.to.RealmTO;
 import org.apache.syncope.common.lib.to.ResourceTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
@@ -68,10 +74,13 @@ import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.AuditLoggerName;
 import org.apache.syncope.common.lib.types.ConnConfProperty;
 import org.apache.syncope.common.lib.types.ConnectorCapability;
+import org.apache.syncope.common.lib.types.IdRepoImplementationType;
+import org.apache.syncope.common.lib.types.ImplementationEngine;
 import org.apache.syncope.common.lib.types.MatchingRule;
 import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.beans.AuditQuery;
 import org.apache.syncope.common.rest.api.beans.ReconQuery;
@@ -593,13 +602,13 @@ public class AuditITCase extends AbstractITCase {
     @Test
     public void issueSYNCOPE1695() {
         // add audit conf for pull
-        AUDIT_SERVICE.set(
-                buildAuditConf("syncope.audit.[PULL]:[USER]:[resource-ldap]:[matchingrule_update]:[SUCCESS]", true));
-        AUDIT_SERVICE.set(
-                buildAuditConf("syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_assign]:[SUCCESS]", true));
-        AUDIT_SERVICE.set(
-                buildAuditConf("syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_provision]:[SUCCESS]",
-                        true));
+        AUDIT_SERVICE.set(buildAuditConf(
+                "syncope.audit.[PULL]:[USER]:[resource-ldap]:[matchingrule_update]:[SUCCESS]", true));
+        AUDIT_SERVICE.set(buildAuditConf(
+                "syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_assign]:[SUCCESS]", true));
+        AUDIT_SERVICE.set(buildAuditConf(
+                "syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_provision]:[SUCCESS]", true));
+
         UserTO pullFromLDAP = null;
         try {
             // pull from resource-ldap -> generates an audit entry
@@ -644,16 +653,57 @@ public class AuditITCase extends AbstractITCase {
                 USER_SERVICE.delete(pullFromLDAP.getKey());
 
                 // restore previous audit
-                AUDIT_SERVICE.set(
-                        buildAuditConf("syncope.audit.[PULL]:[USER]:[resource-ldap]:[matchingrule_update]:[SUCCESS]",
-                                false));
-                AUDIT_SERVICE.set(
-                        buildAuditConf("syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_assign]:[SUCCESS]",
-                                false));
                 AUDIT_SERVICE.set(buildAuditConf(
-                        "syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_provision]:[SUCCESS]",
-                        false));
+                        "syncope.audit.[PULL]:[USER]:[resource-ldap]:[matchingrule_update]:[SUCCESS]", false));
+                AUDIT_SERVICE.set(buildAuditConf(
+                        "syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_assign]:[SUCCESS]", false));
+                AUDIT_SERVICE.set(buildAuditConf(
+                        "syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_provision]:[SUCCESS]", false));
             }
         }
     }
+
+    @Test
+    public void issueSYNCOPE1791() throws IOException {
+        ImplementationTO logicActions;
+        try {
+            logicActions = IMPLEMENTATION_SERVICE.read(
+                    IdRepoImplementationType.LOGIC_ACTIONS, "CustomAuditLogicActions");
+        } catch (SyncopeClientException e) {
+            logicActions = new ImplementationTO();
+            logicActions.setKey("CustomAuditLogicActions");
+            logicActions.setEngine(ImplementationEngine.GROOVY);
+            logicActions.setType(IdRepoImplementationType.LOGIC_ACTIONS);
+            logicActions.setBody(IOUtils.toString(
+                    getClass().getResourceAsStream("/CustomAuditLogicActions.groovy"), StandardCharsets.UTF_8));
+            Response response = IMPLEMENTATION_SERVICE.create(logicActions);
+            logicActions = IMPLEMENTATION_SERVICE.read(
+                    logicActions.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+        }
+        assertNotNull(logicActions);
+
+        RealmTO root = getRealm(SyncopeConstants.ROOT_REALM).orElseThrow();
+        root.getActions().add(logicActions.getKey());
+        REALM_SERVICE.update(root);
+
+        int before = AUDIT_SERVICE.search(
+                new AuditQuery.Builder().type(AuditElements.EventCategoryType.CUSTOM).build()).getTotalCount();
+        try {
+            AUDIT_SERVICE.set(buildAuditConf("syncope.audit.[CUSTOM]:[]:[]:[MY_EVENT]:[SUCCESS]", true));
+
+            AnyObjectTO printer = createAnyObject(AnyObjectITCase.getSample("syncope-1791")).getEntity();
+            updateAnyObject(new AnyObjectUR.Builder(printer.getKey()).
+                    plainAttr(attrAddReplacePatch("location", "new" + getUUIDString())).
+                    build());
+
+            int after = AUDIT_SERVICE.search(
+                    new AuditQuery.Builder().type(AuditElements.EventCategoryType.CUSTOM).build()).getTotalCount();
+            assertEquals(before + 1, after);
+        } finally {
+            AUDIT_SERVICE.set(buildAuditConf("syncope.audit.[CUSTOM]:[]:[]:[MY_EVENT]:[SUCCESS]", false));
+
+            root.getActions().remove(logicActions.getKey());
+            REALM_SERVICE.update(root);
+        }
+    }
 }
diff --git a/fit/core-reference/src/test/resources/CustomAuditLogicActions.groovy b/fit/core-reference/src/test/resources/CustomAuditLogicActions.groovy
new file mode 100644
index 0000000000..08fd8fe04f
--- /dev/null
+++ b/fit/core-reference/src/test/resources/CustomAuditLogicActions.groovy
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+import groovy.transform.CompileStatic
+import java.time.OffsetDateTime
+import org.apache.syncope.common.lib.request.AnyUR
+import org.apache.syncope.common.lib.types.AuditElements
+import org.apache.syncope.core.logic.api.LogicActions
+import org.apache.syncope.core.provisioning.api.AuditManager
+import org.apache.syncope.core.spring.security.AuthContextUtils
+import org.springframework.beans.factory.annotation.Autowired
+
+@CompileStatic
+class CustomAuditLogicActions implements LogicActions {
+
+  @Autowired
+  AuditManager auditManager;
+  
+  @Override
+  <R extends AnyUR> R beforeUpdate(final R input) {
+    auditManager.audit(
+      AuthContextUtils.getWho(),
+      AuditElements.EventCategoryType.CUSTOM,
+      null,
+      null,
+      "MY_EVENT",
+      AuditElements.Result.SUCCESS,
+      "before",
+      OffsetDateTime.now().toString());
+    return input    
+  }
+}