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 2019/12/06 15:59:32 UTC

[syncope] 01/02: SYNCOPE-1511: History management for admin console UI (#141)

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

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

commit 886d9711858389a83586eb883fd685035fd94692
Author: Misagh Moayyed <mm...@gmail.com>
AuthorDate: Thu Dec 5 17:28:12 2019 +0400

    SYNCOPE-1511: History management for admin console UI (#141)
---
 .../console/audit/AuditHistoryDirectoryPanel.java  | 322 +++++++++++++++++++++
 .../client/console/audit/AuditHistoryModal.java    |  55 ++++
 .../client/console/audit/HistoryAuditDetails.java  | 236 +++++++++++++++
 .../client/console/commons/IdRepoConstants.java    |   2 +
 .../console/panels/AnyObjectDirectoryPanel.java    |  23 ++
 .../client/console/panels/DirectoryPanel.java      |   2 +-
 .../client/console/panels/GroupDirectoryPanel.java |  27 +-
 .../client/console/panels/UserDirectoryPanel.java  |  22 ++
 .../console/rest/AuditHistoryRestClient.java       |  57 ++++
 .../wicket/markup/html/form/ActionLink.java        |   1 +
 .../client/console/audit/AuditHistoryModal.html    |  26 ++
 .../AuditHistoryModal.properties}                  |   5 +-
 .../AuditHistoryModal_it.properties}               |   5 +-
 .../AuditHistoryModal_ja.properties}               |   5 +-
 .../AuditHistoryModal_pt_BR.properties}            |   5 +-
 .../AuditHistoryModal_ru.properties}               |   5 +-
 .../client/console/audit/HistoryAuditDetails.html  |  29 ++
 .../HistoryAuditDetails.properties}                |   5 +-
 .../HistoryAuditDetails_it.properties}             |   5 +-
 .../HistoryAuditDetails_ja.properties}             |   5 +-
 .../HistoryAuditDetails_pt_BR.properties}          |   5 +-
 .../HistoryAuditDetails_ru.properties}             |   5 +-
 ...operties => AnyObjectDirectoryPanel.properties} |   3 +-
 ...rties => AnyObjectDirectoryPanel_it.properties} |   3 +-
 ...rties => AnyObjectDirectoryPanel_ja.properties} |   3 +-
 ...es => AnyObjectDirectoryPanel_pt_BR.properties} |   3 +-
 ...rties => AnyObjectDirectoryPanel_ru.properties} |   3 +-
 .../console/panels/GroupDirectoryPanel.properties  |   1 +
 .../panels/GroupDirectoryPanel_it.properties       |   1 +
 .../panels/GroupDirectoryPanel_ja.properties       |   1 +
 .../panels/GroupDirectoryPanel_pt_BR.properties    |   1 +
 .../panels/GroupDirectoryPanel_ru.properties       |   1 +
 .../console/panels/UserDirectoryPanel.properties   |   1 +
 .../panels/UserDirectoryPanel_it.properties        |   1 +
 .../panels/UserDirectoryPanel_ja.properties        |   1 +
 .../panels/UserDirectoryPanel_pt_BR.properties     |   1 +
 .../panels/UserDirectoryPanel_ru.properties        |   1 +
 .../markup/html/form/ActionsPanel.properties       |   4 +
 .../markup/html/form/ActionsPanel_it.properties    |   4 +
 .../markup/html/form/ActionsPanel_ja.properties    |   4 +
 .../markup/html/form/ActionsPanel_pt_BR.properties |   6 +-
 .../markup/html/form/ActionsPanel_ru.properties    |   4 +
 .../src/test/resources/domains/MasterContent.xml   |  27 +-
 .../src/test/resources/domains/MasterContent.xml   |  27 +-
 .../provisioning/api/serialization/POJOHelper.java |  12 +
 .../provisioning/java/DefaultAuditManager.java     |   5 +
 .../java/data/AuditDataBinderImpl.java             |   6 +-
 .../org/apache/syncope/fit/core/AuditITCase.java   |  86 +++++-
 48 files changed, 1002 insertions(+), 60 deletions(-)

diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDirectoryPanel.java
new file mode 100644
index 0000000..e45775e
--- /dev/null
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDirectoryPanel.java
@@ -0,0 +1,322 @@
+/*
+ * 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.client.console.audit;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.audit.AuditHistoryDirectoryPanel.AuditHistoryProvider;
+import org.apache.syncope.client.console.commons.IdRepoConstants;
+import org.apache.syncope.client.console.pages.BasePage;
+import org.apache.syncope.client.console.panels.AjaxDataTablePanel;
+import org.apache.syncope.client.console.panels.DirectoryPanel;
+import org.apache.syncope.client.console.panels.MultilevelPanel;
+import org.apache.syncope.client.console.rest.AnyObjectRestClient;
+import org.apache.syncope.client.console.rest.AuditHistoryRestClient;
+import org.apache.syncope.client.console.rest.GroupRestClient;
+import org.apache.syncope.client.console.rest.UserRestClient;
+import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
+import org.apache.syncope.client.ui.commons.panels.ModalPanel;
+import org.apache.syncope.common.lib.AnyOperations;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.request.AnyObjectUR;
+import org.apache.syncope.common.lib.request.GroupUR;
+import org.apache.syncope.common.lib.request.UserUR;
+import org.apache.syncope.common.lib.to.AnyObjectTO;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.AuditEntryTO;
+import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+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.extensions.markup.html.repeater.util.SortParam;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.StringResourceModel;
+
+public class AuditHistoryDirectoryPanel extends
+        DirectoryPanel<AuditEntryTO, AuditEntryTO, AuditHistoryProvider, AuditHistoryRestClient>
+        implements ModalPanel {
+
+    private static final long serialVersionUID = -8248734710505211261L;
+
+    private static final int TOTAL_AUDIT_HISTORY_COMPARISONS = 25;
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private final BaseModal<?> baseModal;
+
+    private final MultilevelPanel multiLevelPanelRef;
+
+    private final AnyTO anyTO;
+
+    private final AnyTypeKind anyTypeKind;
+
+    public AuditHistoryDirectoryPanel(
+            final BaseModal<?> baseModal,
+            final MultilevelPanel multiLevelPanelRef,
+            final PageReference pageRef,
+            final AnyTO anyTO) {
+
+        super(MultilevelPanel.FIRST_LEVEL_ID, pageRef);
+        disableCheckBoxes();
+
+        this.baseModal = baseModal;
+        this.multiLevelPanelRef = multiLevelPanelRef;
+        this.anyTO = anyTO;
+
+        anyTypeKind = AnyTypeKind.fromTOClass(anyTO.getClass());
+        initResultTable();
+    }
+
+    /**
+     * Restore an object based on the audit record.
+     *
+     * Note that for user objects, the original audit record masks
+     * the password and the security answer; so we cannot use the audit
+     * record to resurrect the entry based on mask data. The method behavior
+     * below will reset the audit record such that the current security answer
+     * and the password for the object are always maintained, and such properties
+     * for the user cannot be restored using audit records.
+     *
+     * @param entryBean the entry bean
+     * @param anyTO the any to
+     * @return the response
+     */
+    private static ProvisioningResult<? extends AnyTO> restore(final AuditEntryTO entryBean,
+            final AnyTO anyTO) {
+        try {
+            String json = getJSONFromAuditEntry(entryBean);
+            if (anyTO instanceof UserTO) {
+                UserTO userTO = MAPPER.readValue(json, UserTO.class);
+                UserUR req = AnyOperations.diff(userTO, anyTO, false);
+                req.setPassword(null);
+                req.setSecurityAnswer(null);
+                return new UserRestClient().update(anyTO.getETagValue(), req);
+            }
+            if (anyTO instanceof GroupTO) {
+                GroupTO groupTO = MAPPER.readValue(json, GroupTO.class);
+                GroupUR req = AnyOperations.diff(groupTO, anyTO, false);
+                return new GroupRestClient().update(anyTO.getETagValue(), req);
+            }
+            if (anyTO instanceof AnyObjectTO) {
+                AnyObjectTO anyObjectTO = MAPPER.readValue(json, AnyObjectTO.class);
+                AnyObjectUR req = AnyOperations.diff(anyObjectTO, anyTO, false);
+                return new AnyObjectRestClient().update(anyTO.getETagValue(), req);
+            }
+        } catch (final Exception e) {
+            LOG.error("Could not restore object for {}", anyTO, e);
+        }
+        throw SyncopeClientException.build(ClientExceptionType.InvalidAnyObject);
+    }
+
+    private static String getJSONFromAuditEntry(final AuditEntryTO entryBean) throws JsonProcessingException {
+        final String json;
+        if (entryBean.getBefore() == null) {
+            json = MAPPER.readTree(entryBean.getOutput()).get("entity").toPrettyString();
+        } else {
+            json = entryBean.getBefore();
+        }
+        return json;
+    }
+
+    private static SortParam<String> getSortParam() {
+        return new SortParam<>("event_date", false);
+    }
+
+    private static AuditElements.Result getQueryableAuditResult() {
+        return AuditElements.Result.SUCCESS;
+    }
+
+    private static List<String> getQueryableAuditEvents() {
+        return Arrays.asList("create", "update");
+    }
+
+    @Override
+    protected AuditHistoryDirectoryPanel.AuditHistoryProvider dataProvider() {
+        return new AuditHistoryProvider(rows);
+    }
+
+    @Override
+    protected String paginatorRowsKey() {
+        return IdRepoConstants.PREF_AUDIT_HISTORY_PAGINATOR_ROWS;
+    }
+
+    @Override
+    protected List<IColumn<AuditEntryTO, String>> getColumns() {
+        final List<IColumn<AuditEntryTO, String>> columns = new ArrayList<>();
+        columns.add(new PropertyColumn<>(
+                new StringResourceModel("who", this), "who"));
+        columns.add(new DatePropertyColumn<>(
+                new StringResourceModel("date", this), null, "date"));
+        return columns;
+    }
+
+    @Override
+    protected void resultTableCustomChanges(
+            final AjaxDataTablePanel.Builder<AuditEntryTO, String> resultTableBuilder) {
+        resultTableBuilder.setMultiLevelPanel(baseModal, multiLevelPanelRef);
+    }
+
+    @Override
+    protected ActionsPanel<AuditEntryTO> getActions(final IModel<AuditEntryTO> model) {
+        final ActionsPanel<AuditEntryTO> panel = super.getActions(model);
+
+        panel.add(new ActionLink<AuditEntryTO>() {
+
+            private static final long serialVersionUID = -6745431735457245600L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final AuditEntryTO modelObject) {
+                AuditHistoryDirectoryPanel.this.getTogglePanel().close(target);
+                viewAuditHistory(modelObject, target);
+                target.add(modal);
+            }
+        }, ActionLink.ActionType.VIEW, IdRepoEntitlement.AUDIT_READ);
+
+        final String auditRestoreEntitlement;
+        switch (this.anyTypeKind) {
+            case USER:
+                auditRestoreEntitlement = IdRepoEntitlement.USER_UPDATE;
+                break;
+            case GROUP:
+                auditRestoreEntitlement = IdRepoEntitlement.GROUP_UPDATE;
+                break;
+            default:
+                auditRestoreEntitlement = IdRepoEntitlement.ANYTYPE_UPDATE;
+                break;
+        }
+
+        panel.add(new ActionLink<AuditEntryTO>() {
+
+            private static final long serialVersionUID = -6745431735457245600L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final AuditEntryTO modelObject) {
+                try {
+                    AuditHistoryDirectoryPanel.this.getTogglePanel().close(target);
+                    ProvisioningResult<? extends AnyTO> result = restore(modelObject, anyTO);
+                    anyTO.setLastChangeDate(new Date(Long.parseLong(result.getEntity().getETagValue())));
+                    target.add(container);
+                } catch (SyncopeClientException e) {
+                    LOG.error("While restoring {}", anyTypeKind, e);
+                    SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage())
+                            ? e.getClass().getName() : e.getMessage());
+                }
+                ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+            }
+        }, ActionLink.ActionType.RESTORE, auditRestoreEntitlement);
+
+        return panel;
+    }
+
+    @Override
+    protected Collection<ActionLink.ActionType> getBatches() {
+        return Collections.emptyList();
+    }
+
+    private void viewAuditHistory(final AuditEntryTO auditEntryBean, final AjaxRequestTarget target) {
+        List<AuditEntryTO> search = AuditHistoryRestClient.search(anyTO.getKey(),
+                0,
+                TOTAL_AUDIT_HISTORY_COMPARISONS,
+                getSortParam(),
+                getQueryableAuditEvents(),
+                getQueryableAuditResult());
+
+        multiLevelPanelRef.next(
+                new StringResourceModel("audit.diff.view", this).getObject(),
+                new HistoryAuditDetails(modal, auditEntryBean,
+                        getPage().getPageReference(), toAuditEntryTOs(search), anyTO, anyTypeKind), target);
+    }
+
+    private List<AuditEntryTO> toAuditEntryTOs(final List<AuditEntryTO> search) {
+        return search
+                .stream()
+                .map(entry -> {
+                    AuditEntryTO bean = new AuditEntryTO();
+                    bean.setKey(anyTO.getKey());
+                    bean.setBefore(entry.getBefore());
+                    bean.setDate(entry.getDate());
+                    bean.setEvent(entry.getEvent());
+                    bean.getInputs().addAll(entry.getInputs());
+                    bean.setLoggerName(entry.getLoggerName());
+                    bean.setOutput(entry.getOutput());
+                    bean.setResult(entry.getResult());
+                    bean.setSubCategory(entry.getSubCategory());
+                    bean.setThrowable(entry.getThrowable());
+                    bean.setWho(entry.getWho());
+                    return bean;
+                })
+                .collect(Collectors.toList());
+    }
+
+    protected class AuditHistoryProvider extends DirectoryDataProvider<AuditEntryTO> {
+
+        private static final long serialVersionUID = 415113175628260864L;
+
+        AuditHistoryProvider(final int paginatorRows) {
+            super(paginatorRows);
+        }
+
+        @Override
+        public Iterator<? extends AuditEntryTO> iterator(final long first, final long count) {
+            return getAuditEntryBeans(first, count).iterator();
+        }
+
+        @Override
+        public long size() {
+            return AuditHistoryRestClient.count(anyTO.getKey(), getQueryableAuditEvents(), getQueryableAuditResult());
+        }
+
+        @Override
+        public IModel<AuditEntryTO> model(final AuditEntryTO auditEntryBean) {
+            return new CompoundPropertyModel<>(auditEntryBean);
+        }
+
+        private List<AuditEntryTO> getAuditEntryBeans(final long first, final long count) {
+            int page = (int) first / paginatorRows;
+            return AuditHistoryRestClient.search(anyTO.getKey(),
+                    Math.max(page, 0) + 1,
+                    Long.valueOf(count).intValue(),
+                    getSortParam(),
+                    getQueryableAuditEvents(),
+                    getQueryableAuditResult());
+        }
+    }
+}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryModal.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryModal.java
new file mode 100644
index 0000000..d4c8d7c
--- /dev/null
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryModal.java
@@ -0,0 +1,55 @@
+/*
+ * 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.client.console.audit;
+
+import org.apache.syncope.client.console.panels.MultilevelPanel;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.ui.commons.panels.ModalPanel;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.markup.html.panel.Panel;
+
+public class AuditHistoryModal<T extends AnyTO> extends Panel implements ModalPanel {
+
+    private static final long serialVersionUID = 1066124171682570080L;
+
+    protected final AuditHistoryDirectoryPanel directoryPanel;
+
+    public AuditHistoryModal(
+            final BaseModal<?> baseModal,
+            final PageReference pageReference,
+            final T entity) {
+
+        super(BaseModal.CONTENT_ID);
+
+        final MultilevelPanel mlp = new MultilevelPanel("history");
+        mlp.setOutputMarkupId(true);
+        this.directoryPanel = getDirectoryPanel(mlp, baseModal, pageReference, entity);
+        add(mlp.setFirstLevel(this.directoryPanel));
+    }
+
+    protected AuditHistoryDirectoryPanel getDirectoryPanel(
+            final MultilevelPanel mlp,
+            final BaseModal<?> baseModal,
+            final PageReference pageReference,
+            final T entity) {
+
+        return new AuditHistoryDirectoryPanel(baseModal, mlp, pageReference, entity);
+    }
+}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/HistoryAuditDetails.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/HistoryAuditDetails.java
new file mode 100644
index 0000000..5e47d7c
--- /dev/null
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/HistoryAuditDetails.java
@@ -0,0 +1,236 @@
+/*
+ * 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.client.console.audit;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.panels.AbstractModalPanel;
+import org.apache.syncope.client.console.panels.MultilevelPanel;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.wicket.markup.html.form.JsonDiffPanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.JsonEditorPanel;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.AuditEntryTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.IChoiceRenderer;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.PropertyModel;
+
+public class HistoryAuditDetails extends MultilevelPanel.SecondLevel {
+
+    private static final String KEY_CURRENT = "current";
+
+    private static final long serialVersionUID = -7400543686272100483L;
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private final AuditEntryTO selected;
+
+    private final List<AuditEntryTO> availableTOs;
+
+    private final AnyTypeKind anyTypeKind;
+
+    private AbstractModalPanel<String> jsonPanel;
+
+    private final AnyTO currentTO;
+
+    public HistoryAuditDetails(final BaseModal<?> baseModal, final AuditEntryTO selected,
+            final PageReference pageRef, final List<AuditEntryTO> availableTOs,
+            final AnyTO currentTO, final AnyTypeKind anyTypeKind) {
+        super();
+        this.availableTOs = availableTOs.stream()
+                .filter(object -> !selected.equals(object) && selected.getBefore() != null)
+                .collect(Collectors.toList());
+        this.selected = selected;
+        this.anyTypeKind = anyTypeKind;
+        this.currentTO = currentTO;
+
+        addCurrentInstanceConf();
+        Form<?> form = initDropdownDiffConfForm();
+        add(form);
+        form.setVisible(!this.availableTOs.isEmpty());
+
+        showConfigurationSinglePanel();
+    }
+
+    private void showConfigurationSinglePanel() {
+        Pair<String, String> info = getJSONInfo(selected);
+
+        jsonPanel = new JsonEditorPanel(null, new PropertyModel<>(info, "right"), true, null) {
+
+            private static final long serialVersionUID = -8927036362466990179L;
+
+            @Override
+            public void onSubmit(final AjaxRequestTarget target) {
+                modal.close(target);
+            }
+        };
+        jsonPanel.setOutputMarkupId(true);
+
+        addOrReplace(jsonPanel);
+    }
+
+    private void showConfigurationDiffPanel(final List<AuditEntryTO> entries) {
+        List<Pair<String, String>> infos = new ArrayList<>();
+        entries.forEach(entry -> infos.add(getJSONInfo(entry)));
+
+        jsonPanel = new JsonDiffPanel(null, new PropertyModel<>(infos.get(0), "value"),
+                new PropertyModel<>(infos.get(1), "value"), null) {
+
+            private static final long serialVersionUID = -8927036362466990179L;
+
+            @Override
+            public void onSubmit(final AjaxRequestTarget target) {
+                modal.close(target);
+            }
+        };
+
+        replace(jsonPanel);
+    }
+
+    private String getSanitizedTOAsJSON(final AnyTO anyTO) throws Exception {
+        if (this.anyTypeKind == AnyTypeKind.USER) {
+            UserTO userTO = (UserTO) anyTO;
+            userTO.setPassword(null);
+            userTO.setSecurityAnswer(null);
+            return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(userTO);
+        }
+        return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(anyTO);
+    }
+
+    private Pair<String, String> getJSONInfo(final AuditEntryTO auditEntryBean) {
+        try {
+            final String content;
+            if (auditEntryBean.getBefore() == null) {
+                content = MAPPER.readTree(auditEntryBean.getOutput()).get("entity").toPrettyString();
+            } else {
+                content = auditEntryBean.getBefore();
+            }
+
+            AnyTO userTO = MAPPER.readValue(content, anyTypeKind.getTOClass());
+            String json = getSanitizedTOAsJSON(userTO);
+            return Pair.of(auditEntryBean.getKey(), json);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static <T extends AuditEntryTO> Map<String, String> getDropdownNamesMap(final List<T> entries) {
+        Map<String, String> map = new LinkedHashMap<>();
+        for (AuditEntryTO audit : entries) {
+            String value = audit.getWho()
+                    + " - " + SyncopeConsoleSession.get().getDateFormat().format(audit.getDate());
+            if (audit.getKey().equalsIgnoreCase(KEY_CURRENT)) {
+                value += " - " + audit.getKey();
+            }
+            map.put(audit.getKey(), value);
+        }
+        return map;
+    }
+
+    private Form<?> initDropdownDiffConfForm() {
+        final Form<AuditEntryTO> form = new Form<>("form");
+        form.setModel(new CompoundPropertyModel<>(selected));
+        form.setOutputMarkupId(true);
+
+        final Map<String, String> namesMap = getDropdownNamesMap(availableTOs);
+        List<String> keys = new ArrayList<>(namesMap.keySet());
+
+        final AjaxDropDownChoicePanel<String> dropdownElem = new AjaxDropDownChoicePanel<>(
+                "compareDropdown",
+                getString("compare"),
+                new PropertyModel<>(selected, "key"),
+                false);
+        dropdownElem.setChoices(keys);
+        dropdownElem.setChoiceRenderer(new IChoiceRenderer<String>() {
+
+            private static final long serialVersionUID = -6265603675261014912L;
+
+            @Override
+            public Object getDisplayValue(final String value) {
+                return namesMap.get(value) == null ? value : namesMap.get(value);
+            }
+
+            @Override
+            public String getIdValue(final String value, final int i) {
+                return value;
+            }
+
+            @Override
+            public String getObject(
+                    final String id, final IModel<? extends List<? extends String>> choices) {
+                return id;
+            }
+        });
+        dropdownElem.setNullValid(false);
+        dropdownElem.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                List<AuditEntryTO> elemsToCompare = new ArrayList<>();
+                elemsToCompare.add(selected);
+
+                final String selectedKey = dropdownElem.getModelObject();
+                if (selectedKey != null) {
+                    if (!selectedKey.isEmpty()) {
+                        AuditEntryTO confToCompare = availableTOs.stream().
+                                filter(object -> object.getKey().equals(selectedKey)).findAny().orElse(null);
+                        elemsToCompare.add(confToCompare);
+                        showConfigurationDiffPanel(elemsToCompare);
+                    } else {
+                        showConfigurationSinglePanel();
+                    }
+                }
+                target.add(jsonPanel);
+            }
+        });
+        form.add(dropdownElem);
+
+        return form;
+    }
+
+    private void addCurrentInstanceConf() {
+        try {
+            AuditEntryTO entryBean = new AuditEntryTO();
+            entryBean.setKey(KEY_CURRENT);
+            entryBean.setWho(currentTO.getCreator());
+            entryBean.setDate(currentTO.getCreationDate());
+            entryBean.setBefore(MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(currentTO));
+            availableTOs.add(entryBean);
+        } catch (final Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoConstants.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoConstants.java
index 52c9932..60dc743 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoConstants.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoConstants.java
@@ -66,6 +66,8 @@ public final class IdRepoConstants {
 
     public static final String PREF_ACCESS_TOKEN_PAGINATOR_ROWS = "accessToken.paginator.rows";
 
+    public static final String PREF_AUDIT_HISTORY_PAGINATOR_ROWS = "audit.history.paginator.rows";
+
     public static final String PREF_NOTIFICATION_PAGINATOR_ROWS = "notification.paginator.rows";
 
     public static final String PREF_IMPLEMENTATION_PAGINATOR_ROWS = "implementation.paginator.rows";
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java
index 2c39540..fe48e23 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java
@@ -26,6 +26,7 @@ import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.commons.IdRepoConstants;
 import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.console.audit.AuditHistoryModal;
 import org.apache.syncope.client.console.notifications.NotificationTasks;
 import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.rest.AnyObjectRestClient;
@@ -46,6 +47,7 @@ import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.ResourceModel;
@@ -170,6 +172,27 @@ public class AnyObjectDirectoryPanel extends AnyDirectoryPanel<AnyObjectTO, AnyO
                 }
             }, ActionType.NOTIFICATION_TASKS, IdRepoEntitlement.TASK_LIST);
         }
+        panel.add(new ActionLink<AnyObjectTO>() {
+
+            private static final long serialVersionUID = -2878723352517770644L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final AnyObjectTO ignore) {
+                IModel<AnyWrapper<AnyObjectTO>> formModel = new CompoundPropertyModel<>(
+                        new AnyWrapper<>(new AnyObjectRestClient().read(model.getObject().getKey())));
+                altDefaultModal.setFormModel(formModel);
+
+                target.add(altDefaultModal.setContent(new AuditHistoryModal<>(
+                        altDefaultModal,
+                        pageRef,
+                        formModel.getObject().getInnerObject())));
+
+                altDefaultModal.header(new StringResourceModel("auditHistory.title", model));
+
+                altDefaultModal.show(true);
+            }
+        }, ActionType.VIEW_AUDIT_HISTORY, IdRepoEntitlement.AUDIT_LIST).
+                setRealms(realm, model.getObject().getDynRealms());
 
         panel.add(new ActionLink<AnyObjectTO>() {
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DirectoryPanel.java
index 326b9e0..8d946d0 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DirectoryPanel.java
@@ -279,7 +279,7 @@ public abstract class DirectoryPanel<
         dataProvider = dataProvider();
 
         final int currentPage = Optional.ofNullable(resultTable)
-            .map(table -> (create ? (int) table.getPageCount() - 1 : (int) table.getCurrentPage())).orElse(0);
+                .map(table -> (create ? (int) table.getPageCount() - 1 : (int) table.getCurrentPage())).orElse(0);
 
         // take care of restClient handle: maybe not useful to keep into
         AjaxDataTablePanel.Builder<T, String> resultTableBuilder = new AjaxDataTablePanel.Builder<T, String>(
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java
index 9754ffd..645e628 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java
@@ -28,6 +28,7 @@ import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.commons.IdRepoConstants;
 import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.console.audit.AuditHistoryModal;
 import org.apache.syncope.client.console.layout.FormLayoutInfoUtils;
 import org.apache.syncope.client.console.notifications.NotificationTasks;
 import org.apache.syncope.client.console.pages.BasePage;
@@ -62,6 +63,7 @@ import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDa
 import org.apache.wicket.event.Broadcast;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.ResourceModel;
@@ -113,7 +115,7 @@ public class GroupDirectoryPanel extends AnyDirectoryPanel<GroupTO, GroupRestCli
                                     SyncopeClient.getUserSearchConditionBuilder().is("key").notNullValue()).query();
 
                             panel = new UserDirectoryPanel.Builder(
-                                AnyTypeClassRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef).
+                                    AnyTypeClassRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef).
                                     setRealm(SyncopeConstants.ROOT_REALM).
                                     setFiltered(true).
                                     setFiql(query).
@@ -133,7 +135,7 @@ public class GroupDirectoryPanel extends AnyDirectoryPanel<GroupTO, GroupRestCli
                                     SyncopeClient.getUserSearchConditionBuilder().is("key").notNullValue()).query();
 
                             panel = new AnyObjectDirectoryPanel.Builder(
-                                AnyTypeClassRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef).
+                                    AnyTypeClassRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef).
                                     setRealm(SyncopeConstants.ROOT_REALM).
                                     setFiltered(true).
                                     setFiql(query).
@@ -343,6 +345,27 @@ public class GroupDirectoryPanel extends AnyDirectoryPanel<GroupTO, GroupRestCli
 
         panel.add(new ActionLink<GroupTO>() {
 
+            private static final long serialVersionUID = -2878723352517770644L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final GroupTO ignore) {
+                IModel<GroupWrapper> formModel = new CompoundPropertyModel<>(
+                        new GroupWrapper(new GroupRestClient().read(model.getObject().getKey())));
+                target.add(altDefaultModal.setContent(new AuditHistoryModal<>(
+                        altDefaultModal,
+                        pageRef,
+                        formModel.getObject().getInnerObject())));
+
+                altDefaultModal.header(new Model<>(
+                        getString("auditHistory.title", new Model<>(new AnyWrapper<>(model.getObject())))));
+
+                altDefaultModal.show(true);
+            }
+        }, ActionType.VIEW_AUDIT_HISTORY, IdRepoEntitlement.AUDIT_LIST).
+                setRealms(realm, model.getObject().getDynRealms());
+
+        panel.add(new ActionLink<GroupTO>() {
+
             private static final long serialVersionUID = -7978723352517770644L;
 
             @Override
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
index 4e083cc..9593000 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
@@ -28,6 +28,7 @@ import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.commons.IdRepoConstants;
 import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.console.audit.AuditHistoryModal;
 import org.apache.syncope.client.console.notifications.NotificationTasks;
 import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.rest.UserRestClient;
@@ -275,6 +276,27 @@ public class UserDirectoryPanel extends AnyDirectoryPanel<UserTO, UserRestClient
 
         panel.add(new ActionLink<UserTO>() {
 
+            private static final long serialVersionUID = -1978723352517770644L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
+                IModel<UserWrapper> formModel = new CompoundPropertyModel<>(
+                        new UserWrapper(new UserRestClient().read(model.getObject().getKey())));
+                target.add(altDefaultModal.setContent(new AuditHistoryModal<>(
+                        altDefaultModal,
+                        pageRef,
+                        formModel.getObject().getInnerObject())));
+
+                altDefaultModal.header(new Model<>(
+                        getString("auditHistory.title", new Model<>(new AnyWrapper<>(model.getObject())))));
+
+                altDefaultModal.show(true);
+            }
+        }, ActionType.VIEW_AUDIT_HISTORY, IdRepoEntitlement.AUDIT_LIST).
+                setRealms(realm, model.getObject().getDynRealms());
+
+        panel.add(new ActionLink<UserTO>() {
+
             private static final long serialVersionUID = -7978723352517770644L;
 
             @Override
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/AuditHistoryRestClient.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/AuditHistoryRestClient.java
new file mode 100644
index 0000000..0818859
--- /dev/null
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/AuditHistoryRestClient.java
@@ -0,0 +1,57 @@
+/*
+ * 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.client.console.rest;
+
+import java.util.List;
+import org.apache.syncope.common.lib.to.AuditEntryTO;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.rest.api.beans.AuditQuery;
+import org.apache.syncope.common.rest.api.service.AuditService;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
+
+public class AuditHistoryRestClient extends BaseRestClient {
+
+    private static final long serialVersionUID = -381814125643246243L;
+
+    public static List<AuditEntryTO> search(
+            final String key,
+            final int page,
+            final int size,
+            final SortParam<String> sort,
+            final List<String> events,
+            final AuditElements.Result result) {
+
+        AuditQuery query = new AuditQuery.Builder(key).
+                page(page).
+                size(size).
+                events(events).
+                result(result).
+                orderBy(toOrderBy(sort)).
+                build();
+        return getService(AuditService.class).search(query).getResult();
+    }
+
+    public static int count(final String key, final List<String> events, final AuditElements.Result result) {
+        AuditQuery query = new AuditQuery.Builder(key).
+                events(events).
+                result(result).
+                build();
+        return getService(AuditService.class).search(query).getTotalCount();
+    }
+}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
index 5416be7..cc0d073 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
@@ -106,6 +106,7 @@ public abstract class ActionLink<T extends Serializable> implements Serializable
         VIEW_DETAILS("read"),
         MANAGE_APPROVAL("edit"),
         EDIT_APPROVAL("edit"),
+        VIEW_AUDIT_HISTORY("read"),
         EXTERNAL_EDITOR("externalEditor");
 
         private final String actionId;
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal.html
new file mode 100644
index 0000000..48c38ea
--- /dev/null
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal.html
@@ -0,0 +1,26 @@
+<!--
+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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+<wicket:panel>
+  <div class="panel-group box-group">
+    <wicket:child/>
+  </div>
+  <span wicket:id="history">[History]</span>
+</wicket:panel>
+</html>
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal.properties
index 07164b4..e2abe89 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal.properties
@@ -14,5 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+who=Who
+date=Date
+audit.diff.view=Configuration
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_it.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_it.properties
index 07164b4..e2abe89 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_it.properties
@@ -14,5 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+who=Who
+date=Date
+audit.diff.view=Configuration
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_ja.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_ja.properties
index 07164b4..e2abe89 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_ja.properties
@@ -14,5 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+who=Who
+date=Date
+audit.diff.view=Configuration
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_pt_BR.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_pt_BR.properties
index 07164b4..e2abe89 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_pt_BR.properties
@@ -14,5 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+who=Who
+date=Date
+audit.diff.view=Configuration
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_ru.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_ru.properties
index 07164b4..e2abe89 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_ru.properties
@@ -14,5 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+who=Who
+date=Date
+audit.diff.view=Configuration
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails.html
new file mode 100644
index 0000000..da89934
--- /dev/null
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails.html
@@ -0,0 +1,29 @@
+<!--
+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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+  <wicket:panel>
+
+    <form wicket:id="form">
+      <span wicket:id="compareDropdown"></span>
+    </form>
+
+    <div wicket:id="content"></div>
+
+  </wicket:panel>
+</html>
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails.properties
index 07164b4..b1406fe 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails.properties
@@ -14,5 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+history.view=History view
+compare=Compare with
+current=Current
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails_it.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails_it.properties
index 07164b4..e128bca 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails_it.properties
@@ -14,5 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+history.view=Storico vista
+compare=paragonare con
+current=presente
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails_ja.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails_ja.properties
index 07164b4..b1406fe 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails_ja.properties
@@ -14,5 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+history.view=History view
+compare=Compare with
+current=Current
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails_pt_BR.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails_pt_BR.properties
index 07164b4..b1406fe 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails_pt_BR.properties
@@ -14,5 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+history.view=History view
+compare=Compare with
+current=Current
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails_ru.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails_ru.properties
index 07164b4..b1406fe 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/HistoryAuditDetails_ru.properties
@@ -14,5 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+history.view=History view
+compare=Compare with
+current=Current
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.properties
index 07164b4..441f190 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.properties
@@ -14,5 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+auditHistory.title=${type} ${name} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel_it.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel_it.properties
index 07164b4..441f190 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel_it.properties
@@ -14,5 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+auditHistory.title=${type} ${name} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel_ja.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel_ja.properties
index 07164b4..441f190 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel_ja.properties
@@ -14,5 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+auditHistory.title=${type} ${name} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel_pt_BR.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel_pt_BR.properties
index 07164b4..441f190 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel_pt_BR.properties
@@ -14,5 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+auditHistory.title=${type} ${name} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel_ru.properties
similarity index 89%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel_ru.properties
index 07164b4..441f190 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel_ru.properties
@@ -14,5 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-any.edit=Edit ${anyTO.type} ${anyTO.name}
-group.members=${right} members of ${left.name}
+auditHistory.title=${type} ${name} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
index 07164b4..7c1cb8c 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel.properties
@@ -16,3 +16,4 @@
 # under the License.
 any.edit=Edit ${anyTO.type} ${anyTO.name}
 group.members=${right} members of ${left.name}
+auditHistory.title=${anyTO.type} ${anyTO.name} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_it.properties
index bd97397..8566276 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_it.properties
@@ -16,3 +16,4 @@
 # under the License.
 any.edit=Modifica ${anyTO.type} ${anyTO.name}
 group.members=Membri ${right} di '${left.name}'
+auditHistory.title=${anyTO.type} ${anyTO.name} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ja.properties
index 0267d65..2f0982a 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ja.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ja.properties
@@ -16,3 +16,4 @@
 # under the License.
 any.edit=${anyTO.type} ${anyTO.name} \u3092\u7de8\u96c6
 group.members=${left.name} \u306e ${right} \u30e1\u30f3\u30d0\u30fc 
+auditHistory.title=${anyTO.type} ${anyTO.name} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_pt_BR.properties
index 9fb316d..b5730e2 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_pt_BR.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_pt_BR.properties
@@ -16,3 +16,4 @@
 # under the License.
 any.edit=Alterar ${anyTO.type} ${anyTO.name}
 group.members=${right} members of ${left.name}
+auditHistory.title=${anyTO.type} ${anyTO.name} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ru.properties
index 94a1bd9..84e3f7b 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ru.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/GroupDirectoryPanel_ru.properties
@@ -17,3 +17,4 @@
 #
 any.edit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c ${anyTO.type} ${anyTO.name}
 group.members=${right} \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438 ${left.name}
+auditHistory.title=${anyTO.type} ${anyTO.name} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel.properties
index 6acf5db..1d4db0d 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel.properties
@@ -18,3 +18,4 @@ any.edit=Edit ${anyTO.type} ${anyTO.username}
 any.propagation.tasks=Propagation tasks for ${type} ${username}
 any.notification.tasks=Notification tasks for ${type} ${username}
 linkedAccounts.title=Manage user accounts
+auditHistory.title=${anyTO.type} ${anyTO.username} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_it.properties
index 38fa14d..ef6c91a 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_it.properties
@@ -18,3 +18,4 @@ any.edit=Modifica ${anyTO.type} ${anyTO.username}
 any.propagation.tasks=Task di propagazione per ${type} ${username}
 any.notification.tasks=Task di notifica per ${type} ${username}
 linkedAccounts.title=Gestisci account utente
+auditHistory.title=${anyTO.type} ${anyTO.username} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ja.properties
index 9107368..a2193e2 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ja.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ja.properties
@@ -18,3 +18,4 @@ any.edit=${anyTO.type} ${anyTO.username} \u3092\u7de8\u96c6
 any.propagation.tasks=${type} ${username} \u306e\u4f1d\u64ad\u30bf\u30b9\u30af
 any.notification.tasks=${type} ${username} \u306e\u901a\u77e5\u30bf\u30b9\u30af
 linkedAccounts.title=\u30e6\u30fc\u30b6\u30fc\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u7ba1\u7406\u3059\u308b
+auditHistory.title=${anyTO.type} ${anyTO.username} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_pt_BR.properties
index 865e74d..84d5d86 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_pt_BR.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_pt_BR.properties
@@ -18,3 +18,4 @@ any.edit=Alterar ${anyTO.type} ${anyTO.username}
 any.propagation.tasks=Propagation tasks for ${type} ${username}
 any.notification.tasks=Notification tasks for ${type} ${username}
 linkedAccounts.title=Gerenciar contas de usu\u00e1rio
+auditHistory.title=${anyTO.type} ${anyTO.username} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ru.properties
index 53e0e86..378c49a 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ru.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ru.properties
@@ -19,3 +19,4 @@ any.edit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c ${anyTO.type} ${anyTO.
 any.propagation.tasks=\u0417\u0430\u0434\u0430\u0447\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f ${type} ${username}
 any.notification.tasks=\u0417\u0430\u0434\u0430\u0447\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u0434\u043b\u044f ${type} ${username}
 linkedAccounts.title=\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u043c\u0438 \u0437\u0430\u043f\u0438\u0441\u044f\u043c\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439
+auditHistory.title=${anyTO.type} ${anyTO.username} history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties
index 3d26fa9..163ece9 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties
@@ -277,3 +277,7 @@ manage_accounts.alt=manage accounts icon
 workflow_modeler.class=fa fa-picture-o
 workflow_modeler.title=workflow modeler
 workflow_modeler.alt=workflow modeler icon
+
+view_audit_history.class=fa fa-history
+view_audit_history.title=history management
+view_audit_history.alt=history management icon
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_it.properties
index ee38bd0..bea031c 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_it.properties
@@ -268,3 +268,7 @@ reconciliation_pull.alt=reconciliation pull icon
 manage_accounts.class=fa fa-users
 manage_accounts.title=gestisci account
 manage_accounts.alt=manage accounts icon
+
+view_audit_history.class=fa fa-history
+view_audit_history.title=storico modifiche
+view_audit_history.alt=storico modifiche icon
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ja.properties
index a6422d1..8b73d1e 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ja.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ja.properties
@@ -269,3 +269,7 @@ reconciliation_pull.alt=\u7167\u5408\u30d7\u30eb icon
 manage_accounts.class=fa fa-users
 manage_accounts.title=\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u7ba1\u7406\u3059\u308b
 manage_accounts.alt=\u30a2\u30ab\u30a6\u30f3\u30c8\u7ba1\u7406\u30a2\u30a4\u30b3\u30f3
+
+view_audit_history.class=fa fa-history
+view_audit_history.title=history management
+view_audit_history.alt=history management icon
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_pt_BR.properties
index ccc7a37..9daea41 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_pt_BR.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_pt_BR.properties
@@ -282,6 +282,6 @@ manage_accounts.class=fa fa-users
 manage_accounts.title=manage accounts
 manage_accounts.alt=manage accounts icon
 
-workflow_modeler.class=fa fa-picture-o
-workflow_modeler.title=workflow modeler
-workflow_modeler.alt=workflow modeler icon
+view_audit_history.class=fa fa-history
+view_audit_history.title=history management
+view_audit_history.alt=history management icon
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ru.properties
index ada11f2..f7a3b90 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ru.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ru.properties
@@ -268,3 +268,7 @@ reconciliation_pull.alt=reconciliation pull icon
 manage_accounts.class=fa fa-users
 manage_accounts.title=manage accounts
 manage_accounts.alt=manage accounts icon
+
+view_audit_history.class=fa fa-history
+view_audit_history.title=history management
+view_audit_history.alt=history management icon
diff --git a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
index c5a7509..5a11d41 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -2340,7 +2340,26 @@ $$ }&#10;
   <SecurityQuestion id="887028ea-66fc-41e7-b397-620d7ea6dfbb" content="What's your mother's maiden name?"/>
 
   <SyncopeLogger logName="syncope.audit.[LOGIC]:[SyncopeLogic]:[]:[isSelfRegAllowed]:[SUCCESS]" logLevel="DEBUG" logType="AUDIT"/>
-  
+
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[assign]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[create]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[delete]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[delete]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[deprovision]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[deprovision]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[link]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[link]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[provision]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[provision]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unassign]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unlink]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[update]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
+
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[assign]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[confirmPasswordReset]:[FAILURE]" logLevel="DEBUG"/>
@@ -2357,12 +2376,8 @@ $$ }&#10;
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[mustChangePassword]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[provision]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[provision]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[read]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[read]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[requestPasswordReset]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[requestPasswordReset]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[search]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[search]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfCreate]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfCreate]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfDelete]:[FAILURE]" logLevel="DEBUG"/>
@@ -2390,8 +2405,6 @@ $$ }&#10;
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[own]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[provisionMembers]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[provision]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[read]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[search]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index 490dc16..8c7fa09 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -2427,7 +2427,26 @@ $$ }&#10;
   <SecurityQuestion id="887028ea-66fc-41e7-b397-620d7ea6dfbb" content="What's your mother's maiden name?"/>
 
   <SyncopeLogger logName="syncope.audit.[LOGIC]:[SyncopeLogic]:[]:[isSelfRegAllowed]:[SUCCESS]" logLevel="DEBUG" logType="AUDIT"/>
-  
+
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[assign]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[create]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[delete]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[delete]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[deprovision]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[deprovision]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[link]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[link]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[provision]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[provision]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unassign]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unlink]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[update]:[FAILURE]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
+
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[assign]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[confirmPasswordReset]:[FAILURE]" logLevel="DEBUG"/>
@@ -2444,12 +2463,8 @@ $$ }&#10;
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[mustChangePassword]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[provision]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[provision]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[read]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[read]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[requestPasswordReset]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[requestPasswordReset]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[search]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[search]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfCreate]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfCreate]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfDelete]:[FAILURE]" logLevel="DEBUG"/>
@@ -2477,8 +2492,6 @@ $$ }&#10;
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[own]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[provisionMembers]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[provision]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[read]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[search]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java
index af99906..c7b8faa 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java
@@ -64,6 +64,18 @@ public final class POJOHelper {
         return result;
     }
 
+    public static String serializeWithDefaultPrettyPrinter(final Object object) {
+        String result = null;
+
+        try {
+            result = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(object);
+        } catch (Exception e) {
+            LOG.error("During serialization", e);
+        }
+
+        return result;
+    }
+
     public static <T extends Object> T deserialize(final String serialized, final Class<T> reference) {
         T result = null;
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAuditManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAuditManager.java
index d5b9df6..374c717 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAuditManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAuditManager.java
@@ -35,6 +35,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Date;
+
 @Transactional(readOnly = true)
 public class DefaultAuditManager implements AuditManager {
 
@@ -52,6 +54,7 @@ public class DefaultAuditManager implements AuditManager {
         AuditEntry auditEntry = AuditEntryImpl.builder().
                 who(who).
                 logger(new AuditLoggerName(type, category, subcategory, event, Result.SUCCESS)).
+                date(new Date()).
                 build();
         org.apache.syncope.core.persistence.api.entity.Logger syncopeLogger =
                 loggerDAO.find(auditEntry.getLogger().toLoggerName());
@@ -64,6 +67,7 @@ public class DefaultAuditManager implements AuditManager {
         auditEntry = AuditEntryImpl.builder()
                 .who(who)
                 .logger(new AuditLoggerName(type, category, subcategory, event, Result.FAILURE))
+                .date(new Date())
                 .build();
         syncopeLogger = loggerDAO.find(auditEntry.getLogger().toLoggerName());
         auditRequested = syncopeLogger != null && syncopeLogger.getLevel() == LoggerLevel.DEBUG;
@@ -110,6 +114,7 @@ public class DefaultAuditManager implements AuditManager {
                 before(before).
                 output(throwable == null ? output : throwable.getMessage()).
                 input(input).
+                date(new Date()).
                 build();
 
         org.apache.syncope.core.persistence.api.entity.Logger syncopeLogger =
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuditDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuditDataBinderImpl.java
index de49702..4937503 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuditDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuditDataBinderImpl.java
@@ -47,18 +47,18 @@ public class AuditDataBinderImpl implements AuditDataBinder {
         }
 
         if (auditEntry.getBefore() != null) {
-            auditEntryTO.setBefore(POJOHelper.serialize(auditEntry.getBefore()));
+            auditEntryTO.setBefore(POJOHelper.serializeWithDefaultPrettyPrinter(auditEntry.getBefore()));
         }
 
         if (auditEntry.getInput() != null) {
             auditEntryTO.getInputs().addAll(Arrays.stream(auditEntry.getInput()).
                     filter(Objects::nonNull).
-                    map(POJOHelper::serialize).
+                    map(POJOHelper::serializeWithDefaultPrettyPrinter).
                     collect(Collectors.toList()));
         }
 
         if (auditEntry.getOutput() != null) {
-            auditEntryTO.setOutput(POJOHelper.serialize(auditEntry.getOutput()));
+            auditEntryTO.setOutput(POJOHelper.serializeWithDefaultPrettyPrinter(auditEntry.getOutput()));
         }
 
         return auditEntryTO;
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 7fa4904..cff5d47 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
@@ -18,22 +18,32 @@
  */
 package org.apache.syncope.fit.core;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.util.List;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.AnyObjectTO;
 import org.apache.syncope.common.lib.to.AuditEntryTO;
 import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.beans.AuditQuery;
 import org.apache.syncope.fit.AbstractITCase;
 import org.junit.jupiter.api.Test;
 
 public class AuditITCase extends AbstractITCase {
 
-    private AuditEntryTO query(final AuditQuery query, final int maxWaitSeconds) {
+    private static final int MAX_WAIT_SECONDS = 30;
+
+    private static AuditEntryTO query(final AuditQuery query, final int maxWaitSeconds, final boolean failIfEmpty) {
         int i = 0;
         List<AuditEntryTO> results = List.of();
         do {
@@ -47,20 +57,39 @@ public class AuditITCase extends AbstractITCase {
             i++;
         } while (results.isEmpty() && i < maxWaitSeconds);
         if (results.isEmpty()) {
-            fail("Timeout when executing query for key " + query.getEntityKey());
+            if (failIfEmpty) {
+                fail("Timeout when executing query for key " + query.getEntityKey());
+            }
+            return null;
         }
 
         return results.get(0);
     }
 
     @Test
+    public void userReadAndSearchYieldsNoAudit() {
+        PagedResult<UserTO> users = userService.search(
+                new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).page(1).size(2).build());
+        assertNotNull(users);
+        assertFalse(users.getResult().isEmpty());
+
+        users.getResult().forEach(user -> assertNotNull(userService.read(user.getKey())));
+
+        users.getResult().forEach(user -> {
+            AuditQuery query = new AuditQuery.Builder(user.getKey()).build();
+            AuditEntryTO entry = query(query, MAX_WAIT_SECONDS, false);
+            assertNull(entry);
+        });
+    }
+
+    @Test
     public void findByUser() {
         UserTO userTO = createUser(UserITCase.getUniqueSample("audit@syncope.org")).getEntity();
         assertNotNull(userTO.getKey());
 
         AuditQuery query = new AuditQuery.Builder(userTO.getKey()).orderBy("event_date desc").
                 page(1).size(1).build();
-        AuditEntryTO entry = query(query, 50);
+        AuditEntryTO entry = query(query, MAX_WAIT_SECONDS, true);
         assertEquals(userTO.getKey(), entry.getKey());
         userService.delete(userTO.getKey());
     }
@@ -79,7 +108,7 @@ public class AuditITCase extends AbstractITCase {
                 event("create").
                 result(AuditElements.Result.SUCCESS).
                 build();
-        AuditEntryTO entry = query(query, 50);
+        AuditEntryTO entry = query(query, MAX_WAIT_SECONDS, true);
         assertEquals(userTO.getKey(), entry.getKey());
         userService.delete(userTO.getKey());
     }
@@ -91,8 +120,51 @@ public class AuditITCase extends AbstractITCase {
 
         AuditQuery query = new AuditQuery.Builder(groupTO.getKey()).orderBy("event_date desc").
                 page(1).size(1).build();
-        AuditEntryTO entry = query(query, 50);
+        AuditEntryTO entry = query(query, MAX_WAIT_SECONDS, true);
         assertEquals(groupTO.getKey(), entry.getKey());
         groupService.delete(groupTO.getKey());
     }
+
+    @Test
+    public void groupReadAndSearchYieldsNoAudit() {
+        PagedResult<GroupTO> groups = groupService.search(
+                new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).build());
+        assertNotNull(groups);
+        assertFalse(groups.getResult().isEmpty());
+
+        groups.getResult().forEach(groupTO -> assertNotNull(groupService.read(groupTO.getKey())));
+
+        groups.getResult().forEach(groupTO -> {
+            AuditQuery query = new AuditQuery.Builder(groupTO.getKey()).build();
+            AuditEntryTO entry = query(query, MAX_WAIT_SECONDS, false);
+            assertNull(entry);
+        });
+    }
+
+    @Test
+    public void findByAnyObject() {
+        AnyObjectTO anyObjectTO = createAnyObject(AnyObjectITCase.getSample("Italy")).getEntity();
+        assertNotNull(anyObjectTO.getKey());
+        AuditQuery query = new AuditQuery.Builder(anyObjectTO.getKey()).orderBy("event_date desc").
+                page(1).size(1).build();
+        AuditEntryTO entry = query(query, MAX_WAIT_SECONDS, true);
+        assertEquals(anyObjectTO.getKey(), entry.getKey());
+        anyObjectService.delete(anyObjectTO.getKey());
+    }
+
+    @Test
+    public void anyObjectReadAndSearchYieldsNoAudit() {
+        PagedResult<AnyObjectTO> anyObjects = anyObjectService.search(
+                new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
+                        fiql(SyncopeClient.getAnyObjectSearchConditionBuilder(getUUIDString()).query()).
+                        build());
+        assertNotNull(anyObjects);
+        assertTrue(anyObjects.getResult().isEmpty());
+
+        anyObjects.getResult().forEach(anyObjectTO -> {
+            AuditQuery query = new AuditQuery.Builder(anyObjectTO.getKey()).build();
+            AuditEntryTO entry = query(query, MAX_WAIT_SECONDS, false);
+            assertNull(entry);
+        });
+    }
 }