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/11/27 16:17:31 UTC
[syncope] branch master updated: SYNCOPE-1511: Provide Audit APIs
(#139)
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
The following commit(s) were added to refs/heads/master by this push:
new 5d3ab98 SYNCOPE-1511: Provide Audit APIs (#139)
5d3ab98 is described below
commit 5d3ab984ff2135d6bf9aded85e98fdf7faa5883c
Author: Misagh Moayyed <mm...@gmail.com>
AuthorDate: Wed Nov 27 18:43:15 2019 +0400
SYNCOPE-1511: Provide Audit APIs (#139)
---
.../apache/syncope/common/lib/to/AuditEntryTO.java | 142 +++++++++++++++++++++
.../common/lib/types/IdRepoEntitlement.java | 2 +
.../syncope/common/rest/api/beans/AuditQuery.java | 51 ++++++++
.../common/rest/api/service/AuditService.java | 54 ++++++++
.../org/apache/syncope/core/logic/AuditLogic.java | 66 ++++++++++
.../core/rest/cxf/service/AbstractAnyService.java | 3 +-
.../core/rest/cxf/service/AuditServiceImpl.java | 47 +++++++
.../syncope/core/persistence/api/dao/AuditDAO.java | 33 +++++
.../core/persistence/api/entity/AuditEntry.java | 43 +++++++
.../src/test/resources/domains/MasterContent.xml | 63 ++++++++-
.../core/persistence/jpa/dao/JPAAuditDAO.java | 103 +++++++++++++++
.../src/test/resources/domains/MasterContent.xml | 57 ++++++++-
.../core/provisioning/api/AuditEntryImpl.java} | 131 +++++++++++++++++--
.../provisioning/api/data/AuditDataBinder.java | 28 ++++
.../provisioning/java/DefaultAuditManager.java | 37 +++---
.../java/data/AuditDataBinderImpl.java | 70 ++++++++++
.../java/job/report/AuditReportlet.java | 6 +-
.../org/apache/syncope/fit/AbstractITCase.java | 4 +
.../org/apache/syncope/fit/core/AuditITCase.java | 76 +++++++++++
19 files changed, 979 insertions(+), 37 deletions(-)
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AuditEntryTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AuditEntryTO.java
new file mode 100644
index 0000000..fbaf0ca
--- /dev/null
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AuditEntryTO.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.to;
+
+import org.apache.syncope.common.lib.BaseBean;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+@XmlRootElement(name = "audit")
+@XmlType
+public class AuditEntryTO extends BaseBean implements EntityTO {
+ private static final long serialVersionUID = 1215115961911228005L;
+
+ private final List<String> inputs = new ArrayList<>();
+
+ private String who;
+
+ private String subCategory;
+
+ private String event;
+
+ private String result;
+
+ private String before;
+
+ private String output;
+
+ private Date date;
+
+ private String throwable;
+
+ private String key;
+
+ private String loggerName;
+
+ public Date getDate() {
+ return date;
+ }
+
+ public void setDate(final Date date) {
+ this.date = date;
+ }
+
+ public String getThrowable() {
+ return throwable;
+ }
+
+ public void setThrowable(final String throwable) {
+ this.throwable = throwable;
+ }
+
+ public String getOutput() {
+ return output;
+ }
+
+ public void setOutput(final String output) {
+ this.output = output;
+ }
+
+ public String getBefore() {
+ return before;
+ }
+
+ public void setBefore(final String before) {
+ this.before = before;
+ }
+
+ public List<String> getInputs() {
+ return inputs;
+ }
+
+ public String getSubCategory() {
+ return subCategory;
+ }
+
+ public void setSubCategory(final String subCategory) {
+ this.subCategory = subCategory;
+ }
+
+ public String getEvent() {
+ return event;
+ }
+
+ public void setEvent(final String event) {
+ this.event = event;
+ }
+
+ public String getResult() {
+ return result;
+ }
+
+ public void setResult(final String result) {
+ this.result = result;
+ }
+
+ public String getWho() {
+ return who;
+ }
+
+ public void setWho(final String who) {
+ this.who = who;
+ }
+
+ public String getLoggerName() {
+ return loggerName;
+ }
+
+ public void setLoggerName(final String loggerName) {
+ this.loggerName = loggerName;
+ }
+
+ @Override
+ public String getKey() {
+ return this.key;
+ }
+
+ @Override
+ public void setKey(final String key) {
+ this.key = key;
+ }
+}
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java
index 583bd41..8ab42af 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java
@@ -110,6 +110,8 @@ public final class IdRepoEntitlement {
public static final String SCHEMA_DELETE = "SCHEMA_DELETE";
+ public static final String AUDIT_SEARCH = "AUDIT_SEARCH";
+
public static final String USER_SEARCH = "USER_SEARCH";
public static final String USER_CREATE = "USER_CREATE";
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuditQuery.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuditQuery.java
new file mode 100644
index 0000000..d8152de
--- /dev/null
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuditQuery.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.rest.api.beans;
+
+import javax.ws.rs.QueryParam;
+
+public class AuditQuery extends AbstractQuery {
+
+ private static final long serialVersionUID = -2863334226169614417L;
+
+ private String key;
+
+ public String getKey() {
+ return key;
+ }
+
+ @QueryParam("key")
+ public void setKey(final String key) {
+ this.key = key;
+ }
+
+ public static class Builder extends AbstractQuery.Builder<AuditQuery, Builder> {
+
+ public Builder key(final String keyword) {
+ getInstance().setKey(keyword);
+ return this;
+ }
+
+ @Override
+ protected AuditQuery newInstance() {
+ return new AuditQuery();
+ }
+ }
+
+}
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuditService.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuditService.java
new file mode 100644
index 0000000..897131e
--- /dev/null
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuditService.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.rest.api.service;
+
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.apache.syncope.common.lib.to.AuditEntryTO;
+import org.apache.syncope.common.lib.to.PagedResult;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.beans.AuditQuery;
+
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * REST operations for audit events.
+ */
+@Tag(name = "Audits")
+@SecurityRequirements({
+ @SecurityRequirement(name = "BasicAuthentication"),
+ @SecurityRequirement(name = "Bearer")})
+@Path("audits")
+public interface AuditService {
+
+ /**
+ * Returns a paged list of audit objects matching the given query.
+ *
+ * @param auditQuery query conditions
+ * @return paged list of objects matching the given query
+ */
+ @GET
+ @Produces({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML})
+ PagedResult<AuditEntryTO> search(@BeanParam AuditQuery auditQuery);
+}
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
new file mode 100644
index 0000000..6fd482d
--- /dev/null
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.to.AuditEntryTO;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.core.persistence.api.dao.AuditDAO;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.entity.AuditEntry;
+import org.apache.syncope.core.provisioning.api.data.AuditDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class AuditLogic extends AbstractTransactionalLogic<AuditEntryTO> {
+
+ @Autowired
+ private AuditDataBinder binder;
+
+ @Autowired
+ private AuditDAO auditDAO;
+
+ @PreAuthorize("hasRole('" + IdRepoEntitlement.AUDIT_SEARCH + "')")
+ @Transactional(readOnly = true)
+ public Pair<Integer, List<AuditEntryTO>> search(
+ final String key,
+ final int page,
+ final int size,
+ final List<OrderByClause> orderByClauses) {
+
+ Integer count = auditDAO.count(key);
+ List<AuditEntry> matching = auditDAO.findByEntityKey(key, page, size, orderByClauses);
+ List<AuditEntryTO> results = matching.stream().
+ map(audit -> binder.returnAuditTO(binder.getAuditTO(audit))).
+ collect(Collectors.toList());
+ return Pair.of(count, results);
+ }
+
+ @Override
+ protected AuditEntryTO resolveReference(final Method method, final Object... args)
+ throws UnresolvedReferenceException {
+ throw new UnresolvedReferenceException();
+ }
+}
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
index 24e80d0..1485786 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
@@ -192,7 +192,8 @@ public abstract class AbstractAnyService<TO extends AnyTO, CR extends AnyCR, UR
@Override
public void delete(final String key, final SchemaType schemaType, final String schema) {
- addUpdateOrReplaceAttr(getActualKey(getAnyDAO(), key),
+ addUpdateOrReplaceAttr(
+ getActualKey(getAnyDAO(), key),
schemaType,
new Attr.Builder(schema).build(),
PatchOperation.DELETE);
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuditServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuditServiceImpl.java
new file mode 100644
index 0000000..df1c978
--- /dev/null
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuditServiceImpl.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.rest.cxf.service;
+
+import java.util.List;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.to.AuditEntryTO;
+import org.apache.syncope.common.lib.to.PagedResult;
+import org.apache.syncope.common.rest.api.beans.AuditQuery;
+import org.apache.syncope.common.rest.api.service.AuditService;
+import org.apache.syncope.core.logic.AuditLogic;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AuditServiceImpl extends AbstractServiceImpl implements AuditService {
+
+ @Autowired
+ private AuditLogic logic;
+
+ @Override
+ public PagedResult<AuditEntryTO> search(final AuditQuery auditQuery) {
+ Pair<Integer, List<AuditEntryTO>> result = logic.search(
+ auditQuery.getKey(),
+ auditQuery.getPage(),
+ auditQuery.getSize(),
+ getOrderByClauses(auditQuery.getOrderBy()));
+
+ return buildPagedResult(result.getRight(), auditQuery.getPage(), auditQuery.getSize(), result.getLeft());
+ }
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditDAO.java
new file mode 100644
index 0000000..0d1692d
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditDAO.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.dao;
+
+import java.util.List;
+
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.entity.AuditEntry;
+
+public interface AuditDAO {
+
+ String TABLE_NAME = "SYNCOPEAUDIT";
+
+ List<AuditEntry> findByEntityKey(String key, int page, int size, List<OrderByClause> orderByClauses);
+
+ Integer count(String key);
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AuditEntry.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AuditEntry.java
new file mode 100644
index 0000000..7bcb134
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AuditEntry.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.entity;
+
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public interface AuditEntry extends Serializable {
+
+ String getWho();
+
+ AuditLoggerName getLogger();
+
+ Object getBefore();
+
+ Object getOutput();
+
+ Object[] getInput();
+
+ String getThrowable();
+
+ Date getDate();
+
+ String getKey();
+}
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 cf38a3d..c5a7509 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -2336,11 +2336,66 @@ $$ }
<Implementation id="ReconciliationReportletConf" type="REPORTLET" engine="JAVA"
body='{"@class":"org.apache.syncope.common.lib.report.ReconciliationReportletConf","name":"dashboardReconciliationReportlet","userMatchingCond":null,"groupMatchingCond":null,"anyObjectMatchingCond":null,"features":["key","username","groupName"]}'/>
<ReportReportlet report_id="c3520ad9-179f-49e7-b315-d684d216dd97" implementation_id="ReconciliationReportletConf"/>
-
- <SyncopeLogger logName="syncope.audit.[LOGIC]:[SyncopeLogic]:[]:[isSelfRegAllowed]:[SUCCESS]" logLevel="DEBUG" logType="AUDIT"/>
-
+
<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"/>
- <GatewayRoute id="ec7bada2-3dd6-460c-8441-65521d005ffa" name="basic1" target="http://httpbin.org:80" status="PUBLISHED"
+ <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"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[confirmPasswordReset]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[create]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[delete]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[delete]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[deprovision]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[deprovision]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[link]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[link]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[mustChangePassword]:[FAILURE]" logLevel="DEBUG"/>
+ <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"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfDelete]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfRead]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfRead]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfStatus]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfStatus]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfUpdate]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfUpdate]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[status]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[status]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unassign]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unlink]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[update]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
+
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[delete]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[deprovision]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[link]:[SUCCESS]" logLevel="DEBUG"/>
+ <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"/>
+
+ <GatewayRoute id="ec7bada2-3dd6-460c-8441-65521d005ffa" name="basic1" target="http://httpbin.org:80" status="PUBLISHED"
predicates="[{"cond":null,"factory":"METHOD","args":"GET"}]"/>
</dataset>
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditDAO.java
new file mode 100644
index 0000000..5f56392
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditDAO.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.dao;
+
+import javax.sql.DataSource;
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.apache.syncope.core.persistence.api.DomainHolder;
+import org.apache.syncope.core.persistence.api.dao.AuditDAO;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.entity.AuditEntry;
+import org.apache.syncope.core.persistence.jpa.entity.AbstractEntity;
+import org.apache.syncope.core.provisioning.api.AuditEntryImpl;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional(rollbackFor = Throwable.class)
+@Repository
+public class JPAAuditDAO extends AbstractDAO<AbstractEntity> implements AuditDAO {
+
+ @Autowired
+ private DomainHolder domainHolder;
+
+ private static String buildWhereClauseForEntityKey(final String key) {
+ return " WHERE MESSAGE LIKE '%" + key + "%' ";
+ }
+
+ @Override
+ public List<AuditEntry> findByEntityKey(
+ final String key,
+ final int page,
+ final int itemsPerPage,
+ final List<OrderByClause> orderByClauses) {
+
+ try {
+ String queryString = "SELECT * FROM " + AuditDAO.TABLE_NAME + buildWhereClauseForEntityKey(key);
+ if (!orderByClauses.isEmpty()) {
+ queryString += " ORDER BY " + orderByClauses.stream().
+ map(orderBy -> orderBy.getField() + ' ' + orderBy.getDirection().name()).
+ collect(Collectors.joining(","));
+ }
+ JdbcTemplate template = getJdbcTemplate();
+ template.setMaxRows(itemsPerPage);
+ template.setFetchSize(itemsPerPage * (page <= 0 ? 0 : page - 1));
+ return template.query(queryString, (resultSet, i) -> {
+ AuditEntryImpl entry = POJOHelper.deserialize(resultSet.getString("MESSAGE"), AuditEntryImpl.class);
+ String throwable = resultSet.getString("THROWABLE");
+ entry.setThrowable(throwable);
+ Timestamp date = resultSet.getTimestamp("EVENT_DATE");
+ entry.setDate(new Date(date.getTime()));
+ entry.setKey(key);
+ return entry;
+ });
+ } catch (Exception e) {
+ LOG.error("Unable to execute search query to find entity " + key, e);
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Integer count(final String key) {
+ try {
+ String queryString = "SELECT COUNT(0) FROM " + AuditDAO.TABLE_NAME + buildWhereClauseForEntityKey(key);
+ return Objects.requireNonNull(getJdbcTemplate().queryForObject(queryString, Integer.class));
+ } catch (Exception e) {
+ LOG.error("Unable to execute count query for entity " + key, e);
+ }
+ return 0;
+ }
+
+ private JdbcTemplate getJdbcTemplate() {
+ String domain = AuthContextUtils.getDomain();
+ DataSource datasource = domainHolder.getDomains().get(domain);
+ if (datasource == null) {
+ throw new IllegalArgumentException("Could not get to DataSource for domain " + domain);
+ }
+ return new JdbcTemplate(datasource);
+ }
+}
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index e9ba453..490dc16 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -2424,9 +2424,64 @@ $$ }
body='{"@class":"org.apache.syncope.common.lib.report.ReconciliationReportletConf","name":"dashboardReconciliationReportlet","userMatchingCond":null,"groupMatchingCond":null,"anyObjectMatchingCond":null,"features":["key","username","groupName"]}'/>
<ReportReportlet report_id="c3520ad9-179f-49e7-b315-d684d216dd97" implementation_id="ReconciliationReportletConf"/>
+ <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"/>
- <SecurityQuestion id="887028ea-66fc-41e7-b397-620d7ea6dfbb" content="What's your mother's maiden name?"/>
+ <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"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[confirmPasswordReset]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[create]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[delete]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[delete]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[deprovision]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[deprovision]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[link]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[link]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[mustChangePassword]:[FAILURE]" logLevel="DEBUG"/>
+ <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"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfDelete]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfRead]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfRead]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfStatus]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfStatus]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfUpdate]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfUpdate]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[status]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[status]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unassign]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unlink]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[update]:[FAILURE]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
+
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[delete]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[deprovision]:[SUCCESS]" logLevel="DEBUG"/>
+ <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[link]:[SUCCESS]" logLevel="DEBUG"/>
+ <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"/>
<GatewayRoute id="ec7bada2-3dd6-460c-8441-65521d005ffa" name="basic1" target="http://httpbin.org:80" status="PUBLISHED"
predicates="[{"cond":null,"factory":"METHOD","args":"GET"}]"/>
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/AuditEntry.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/AuditEntryImpl.java
similarity index 55%
rename from core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/AuditEntry.java
rename to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/AuditEntryImpl.java
index c0cd4cf..e64a911 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/AuditEntry.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/AuditEntryImpl.java
@@ -16,19 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.syncope.core.provisioning.java;
+package org.apache.syncope.core.provisioning.api;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
-import java.io.Serializable;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.request.UserUR;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.core.persistence.api.entity.AuditEntry;
-public class AuditEntry implements Serializable {
+import java.util.Date;
+
+public class AuditEntryImpl implements AuditEntry {
private static final long serialVersionUID = -2299082316063743582L;
@@ -44,13 +46,19 @@ public class AuditEntry implements Serializable {
private final Object[] input;
+ private String throwable;
+
+ private Date date;
+
+ private String key;
+
@JsonCreator
- public AuditEntry(
- @JsonProperty("who") final String who,
- @JsonProperty("logger") final AuditLoggerName logger,
- @JsonProperty("before") final Object before,
- @JsonProperty("output") final Object output,
- @JsonProperty("input") final Object[] input) {
+ public AuditEntryImpl(
+ @JsonProperty("who") final String who,
+ @JsonProperty("logger") final AuditLoggerName logger,
+ @JsonProperty("before") final Object before,
+ @JsonProperty("output") final Object output,
+ @JsonProperty("input") final Object[] input) {
super();
@@ -95,23 +103,128 @@ public class AuditEntry implements Serializable {
return masked;
}
+ @Override
public String getWho() {
return who;
}
+ @Override
public AuditLoggerName getLogger() {
return logger;
}
+ @Override
public Object getBefore() {
return before;
}
+ @Override
public Object getOutput() {
return output;
}
+ @Override
public Object[] getInput() {
return input;
}
+
+ @Override
+ public String getThrowable() {
+ return throwable;
+ }
+
+ public void setThrowable(final String throwable) {
+ this.throwable = throwable;
+ }
+
+ @Override
+ public Date getDate() {
+ return date;
+ }
+
+ public void setDate(final Date date) {
+ this.date = date;
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(final String key) {
+ this.key = key;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder {
+ private String who;
+
+ private AuditLoggerName logger;
+
+ private Object before;
+
+ private Object output;
+
+ private Object[] input;
+
+ private String throwable;
+
+ private Date date;
+
+ private String key;
+
+ private Builder() {
+ }
+
+ public Builder date(final Date date) {
+ this.date = date;
+ return this;
+ }
+
+ public Builder throwable(final String throwable) {
+ this.throwable = throwable;
+ return this;
+ }
+
+ public Builder key(final String key) {
+ this.key = key;
+ return this;
+ }
+
+ public Builder who(final String who) {
+ this.who = who;
+ return this;
+ }
+
+ public Builder logger(final AuditLoggerName logger) {
+ this.logger = logger;
+ return this;
+ }
+
+ public Builder before(final Object before) {
+ this.before = before;
+ return this;
+ }
+
+ public Builder output(final Object output) {
+ this.output = output;
+ return this;
+ }
+
+ public Builder input(final Object[] input) {
+ this.input = input;
+ return this;
+ }
+
+ public AuditEntryImpl build() {
+ AuditEntryImpl entry = new AuditEntryImpl(who, logger, before, output, input);
+ entry.setDate(date);
+ entry.setThrowable(throwable);
+ entry.setKey(key);
+ return entry;
+ }
+ }
}
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AuditDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AuditDataBinder.java
new file mode 100644
index 0000000..81e3241
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AuditDataBinder.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.api.data;
+
+import org.apache.syncope.common.lib.to.AuditEntryTO;
+import org.apache.syncope.core.persistence.api.entity.AuditEntry;
+
+public interface AuditDataBinder {
+ AuditEntryTO getAuditTO(AuditEntry application);
+
+ AuditEntryTO returnAuditTO(AuditEntryTO user);
+}
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 3d44e35..0f9e169 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
@@ -18,6 +18,8 @@
*/
package org.apache.syncope.core.provisioning.java;
+import org.apache.syncope.core.persistence.api.entity.AuditEntry;
+import org.apache.syncope.core.provisioning.api.AuditEntryImpl;
import org.apache.syncope.core.provisioning.api.AuditManager;
import org.apache.syncope.common.lib.types.AuditElements;
import org.apache.syncope.common.lib.types.AuditElements.Result;
@@ -47,12 +49,10 @@ public class DefaultAuditManager implements AuditManager {
final String subcategory,
final String event) {
- AuditEntry auditEntry = new AuditEntry(
- who,
- new AuditLoggerName(type, category, subcategory, event, Result.SUCCESS),
- null,
- null,
- null);
+ AuditEntry auditEntry = AuditEntryImpl.builder()
+ .who(who)
+ .logger(new AuditLoggerName(type, category, subcategory, event, Result.SUCCESS))
+ .build();
org.apache.syncope.core.persistence.api.entity.Logger syncopeLogger =
loggerDAO.find(auditEntry.getLogger().toLoggerName());
boolean auditRequested = syncopeLogger != null && syncopeLogger.getLevel() == LoggerLevel.DEBUG;
@@ -61,12 +61,10 @@ public class DefaultAuditManager implements AuditManager {
return true;
}
- auditEntry = new AuditEntry(
- who,
- new AuditLoggerName(type, category, subcategory, event, Result.FAILURE),
- null,
- null,
- null);
+ auditEntry = AuditEntryImpl.builder()
+ .who(who)
+ .logger(new AuditLoggerName(type, category, subcategory, event, Result.FAILURE))
+ .build();
syncopeLogger = loggerDAO.find(auditEntry.getLogger().toLoggerName());
auditRequested = syncopeLogger != null && syncopeLogger.getLevel() == LoggerLevel.DEBUG;
@@ -106,13 +104,14 @@ public class DefaultAuditManager implements AuditManager {
throwable = (Throwable) output;
}
- AuditEntry auditEntry = new AuditEntry(
- who,
- new AuditLoggerName(type, category, subcategory, event, condition),
- before,
- throwable == null ? output : throwable.getMessage(),
- input);
-
+ AuditEntry auditEntry = AuditEntryImpl.builder()
+ .who(who)
+ .logger(new AuditLoggerName(type, category, subcategory, event, condition))
+ .before(before)
+ .output(throwable == null ? output : throwable.getMessage())
+ .input(input)
+ .build();
+
org.apache.syncope.core.persistence.api.entity.Logger syncopeLogger =
loggerDAO.find(auditEntry.getLogger().toLoggerName());
if (syncopeLogger != null && syncopeLogger.getLevel() == LoggerLevel.DEBUG) {
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
new file mode 100644
index 0000000..6a34094
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuditDataBinderImpl.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.data;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.syncope.common.lib.to.AuditEntryTO;
+import org.apache.syncope.core.persistence.api.entity.AuditEntry;
+import org.apache.syncope.core.provisioning.api.data.AuditDataBinder;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AuditDataBinderImpl implements AuditDataBinder {
+
+ @Override
+ public AuditEntryTO getAuditTO(final AuditEntry auditEntry) {
+ AuditEntryTO auditTO = new AuditEntryTO();
+ auditTO.setKey(auditEntry.getKey());
+ auditTO.setWho(auditEntry.getWho());
+ auditTO.setDate(auditEntry.getDate());
+ auditTO.setThrowable(auditEntry.getThrowable());
+ auditTO.setLoggerName(auditEntry.getLogger().toLoggerName());
+
+ auditTO.setSubCategory(auditEntry.getLogger().getSubcategory());
+ auditTO.setEvent(auditEntry.getLogger().getEvent());
+
+ if (auditEntry.getLogger().getResult() != null) {
+ auditTO.setResult(auditEntry.getLogger().getResult().name());
+ }
+
+ if (auditEntry.getBefore() != null) {
+ auditTO.setBefore(ToStringBuilder.reflectionToString(auditEntry.getBefore(), ToStringStyle.JSON_STYLE));
+ }
+
+ if (auditEntry.getInput() != null) {
+ auditTO.getInputs().addAll(Arrays.stream(auditEntry.getInput()).
+ map(input -> ToStringBuilder.reflectionToString(input, ToStringStyle.JSON_STYLE)).
+ collect(Collectors.toList()));
+ }
+
+ if (auditEntry.getOutput() != null) {
+ auditTO.setOutput(ToStringBuilder.reflectionToString(auditEntry.getOutput(), ToStringStyle.JSON_STYLE));
+ }
+
+ return auditTO;
+ }
+
+ @Override
+ public AuditEntryTO returnAuditTO(final AuditEntryTO auditEntryTO) {
+ return auditEntryTO;
+ }
+}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
index 84483c6..894d3e1 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
@@ -28,7 +28,8 @@ import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.syncope.common.lib.report.AuditReportletConf;
import org.apache.syncope.common.lib.report.ReportletConf;
import org.apache.syncope.core.persistence.api.DomainHolder;
-import org.apache.syncope.core.provisioning.java.AuditEntry;
+import org.apache.syncope.core.persistence.api.entity.AuditEntry;
+import org.apache.syncope.core.provisioning.api.AuditEntryImpl;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
import org.apache.syncope.core.persistence.api.dao.ReportletConfClass;
@@ -59,7 +60,7 @@ public class AuditReportlet extends AbstractReportlet {
handler.startElement("", "", "events", null);
AttributesImpl atts = new AttributesImpl();
for (Map<String, Object> row : rows) {
- AuditEntry auditEntry = POJOHelper.deserialize(row.get("MESSAGE").toString(), AuditEntry.class);
+ AuditEntry auditEntry = POJOHelper.deserialize(row.get("MESSAGE").toString(), AuditEntryImpl.class);
atts.clear();
if (StringUtils.isNotBlank(auditEntry.getWho())) {
@@ -147,5 +148,4 @@ public class AuditReportlet extends AbstractReportlet {
doExtractConf(handler, status);
}
-
}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index be5f0cb..5f0c224 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -85,6 +85,7 @@ import org.apache.syncope.common.rest.api.service.AnyObjectService;
import org.apache.syncope.common.rest.api.service.AnyTypeClassService;
import org.apache.syncope.common.rest.api.service.AnyTypeService;
import org.apache.syncope.common.rest.api.service.ApplicationService;
+import org.apache.syncope.common.rest.api.service.AuditService;
import org.apache.syncope.common.rest.api.service.CamelRouteService;
import org.apache.syncope.common.rest.api.service.ConnectorHistoryService;
import org.apache.syncope.common.rest.api.service.ConnectorService;
@@ -290,6 +291,8 @@ public abstract class AbstractITCase {
protected static SCIMConfService scimConfService;
+ protected static AuditService auditService;
+
@BeforeAll
public static void securitySetup() {
try (InputStream propStream = Encryptor.class.getResourceAsStream("/security.properties")) {
@@ -361,6 +364,7 @@ public abstract class AbstractITCase {
oidcClientService = adminClient.getService(OIDCClientService.class);
oidcProviderService = adminClient.getService(OIDCProviderService.class);
scimConfService = adminClient.getService(SCIMConfService.class);
+ auditService = adminClient.getService(AuditService.class);
}
@Autowired
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
new file mode 100644
index 0000000..2bedab7
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.fit.core;
+
+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 java.util.List;
+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.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 String key, final int maxWaitSeconds) {
+ int i = 0;
+ List<AuditEntryTO> results = List.of();
+ do {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ }
+
+ results = auditService.search(new AuditQuery.Builder().
+ key(key).orderBy("event_date desc").page(1).size(1).build()).getResult();
+
+ i++;
+ } while (results.isEmpty() && i < maxWaitSeconds);
+ if (results.isEmpty()) {
+ fail("Timeout when executing query for key " + key);
+ }
+
+ return results.get(0);
+ }
+
+ @Test
+ public void findByUser() {
+ UserTO userTO = createUser(UserITCase.getUniqueSample("audit@syncope.org")).getEntity();
+ assertNotNull(userTO.getKey());
+
+ AuditEntryTO entry = query(userTO.getKey(), 50);
+ assertEquals(userTO.getKey(), entry.getKey());
+ userService.delete(userTO.getKey());
+ }
+
+ @Test
+ public void findByGroup() {
+ GroupTO groupTO = createGroup(GroupITCase.getBasicSample("AuditGroup")).getEntity();
+ assertNotNull(groupTO.getKey());
+
+ AuditEntryTO entry = query(groupTO.getKey(), 50);
+ assertEquals(groupTO.getKey(), entry.getKey());
+ groupService.delete(groupTO.getKey());
+ }
+}