This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch 4_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/4_1_X by this push:
new 74ca766cb2 [SYNCOPE-1978] Search audit events by username (#1443)
74ca766cb2 is described below
commit 74ca766cb28bedb8409ee64aec2181b31740cd7c
Author: Oleg Zimakov <[email protected]>
AuthorDate: Sat Jun 27 00:04:24 2026 -0700
[SYNCOPE-1978] Search audit events by username (#1443)
---
.../syncope/common/rest/api/beans/AuditQuery.java | 20 ++++++
.../org/apache/syncope/core/logic/AuditLogic.java | 6 +-
.../core/rest/cxf/service/AuditServiceImpl.java | 1 +
.../core/persistence/api/dao/AuditEventDAO.java | 2 +
.../core/persistence/jpa/dao/JPAAuditEventDAO.java | 29 ++++++++
.../persistence/neo4j/dao/Neo4jAuditEventDAO.java | 14 ++++
.../dao/ElasticsearchAuditEventDAO.java | 18 ++++-
.../opensearch/dao/OpenSearchAuditEventDAO.java | 18 ++++-
.../org/apache/syncope/fit/core/AuditITCase.java | 81 ++++++++++++++++++++++
9 files changed, 183 insertions(+), 6 deletions(-)
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
index 9d82c912a0..631057691e 100644
---
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
@@ -43,6 +43,11 @@ public class AuditQuery extends AbstractTimeframeQuery {
return this;
}
+ public Builder username(final String username) {
+ getInstance().getUsername().add(username);
+ return this;
+ }
+
public Builder who(final String who) {
getInstance().getWho().add(who);
return this;
@@ -76,6 +81,8 @@ public class AuditQuery extends AbstractTimeframeQuery {
private String entityKey;
+ private Set<String> username = new HashSet<>();
+
private Set<String> who = new HashSet<>();
private OpEvent.CategoryType type;
@@ -99,6 +106,19 @@ public class AuditQuery extends AbstractTimeframeQuery {
this.entityKey = entityKey;
}
+ @Parameter(name = "username", description = "username(s) embedded in the
audited payload to match "
+ + "(the affected entity, as opposed to the 'who' author); "
+ + "may be repeated to match any of the given values", array =
+ @ArraySchema(schema = @Schema(implementation = String.class)))
+ public Set<String> getUsername() {
+ return username;
+ }
+
+ @QueryParam("username")
+ public void setUsername(final Set<String> username) {
+ this.username = username;
+ }
+
@Parameter(name = "who", description = "audit event author(s) (username)
to match; "
+ "may be repeated to match any of the given values", array =
@ArraySchema(schema = @Schema(implementation = String.class)))
diff --git
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
index 291c5291e6..cff41724a4 100644
---
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
+++
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
@@ -275,6 +275,7 @@ public class AuditLogic extends
AbstractTransactionalLogic<AuditConfTO> {
@Transactional(readOnly = true)
public Page<AuditEventTO> search(
final String entityKey,
+ final Set<String> username,
final Set<String> who,
final OpEvent.CategoryType type,
final String category,
@@ -285,10 +286,11 @@ public class AuditLogic extends
AbstractTransactionalLogic<AuditConfTO> {
final OffsetDateTime after,
final Pageable pageable) {
- long count = auditEventDAO.count(entityKey, who, type, category,
subcategory, op, result, before, after);
+ long count = auditEventDAO.count(
+ entityKey, username, who, type, category, subcategory, op,
result, before, after);
List<AuditEventTO> matching = auditEventDAO.search(
- entityKey, who, type, category, subcategory, op, result,
before, after, pageable);
+ entityKey, username, who, type, category, subcategory, op,
result, before, after, pageable);
return new SyncopePage<>(matching, pageable, count);
}
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
index 468830cf5e..0580f0953a 100644
---
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
@@ -70,6 +70,7 @@ public class AuditServiceImpl extends AbstractService
implements AuditService {
public PagedResult<AuditEventTO> search(final AuditQuery auditQuery) {
Page<AuditEventTO> result = logic.search(
auditQuery.getEntityKey(),
+ auditQuery.getUsername(),
auditQuery.getWho(),
auditQuery.getType(),
auditQuery.getCategory(),
diff --git
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditEventDAO.java
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditEventDAO.java
index a2f9e2a107..5e83015b95 100644
---
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditEventDAO.java
+++
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditEventDAO.java
@@ -32,6 +32,7 @@ public interface AuditEventDAO {
long count(
String entityKey,
+ Set<String> username,
Set<String> who,
OpEvent.CategoryType type,
String category,
@@ -56,6 +57,7 @@ public interface AuditEventDAO {
List<AuditEventTO> search(
String entityKey,
+ Set<String> username,
Set<String> who,
OpEvent.CategoryType type,
String category,
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditEventDAO.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditEventDAO.java
index c9d9d8780c..e09e70286f 100644
---
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditEventDAO.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditEventDAO.java
@@ -63,6 +63,31 @@ public class JPAAuditEventDAO extends AbstractAuditEventDAO
implements AuditEven
return this;
}
+ // Unlike the entityKey predicate above (a constrained UUID,
concatenated), the free-form username
+ // is bound as a query parameter and its LIKE metacharacters are
escaped, so '%'/'_' in a username
+ // match literally and there is no injection surface.
+ // '#' is used as the LIKE escape char (instead of '\') and applied
uniformly: a single '\' in
+ // native SQL is mishandled by MySQL/MariaDB and Oracle has no default
LIKE escape, whereas '#'
+ // works across every supported database (this DAO is not subclassed
per-database).
+ protected static String escapeForLike(final String value) {
+ return value.replace("#", "##").replace("%", "#%").replace("_",
"#_");
+ }
+
+ public AuditEventCriteriaBuilder username(final Set<String> username,
final List<Object> parameters) {
+ if (!CollectionUtils.isEmpty(username)) {
+ query.append(andIfNeeded()).append("(").
+ append(username.stream().map(value -> {
+ String pattern = "%\"username\":\"" +
escapeForLike(value) + "\"%";
+ return "(beforeValue LIKE ?" +
setParameter(parameters, pattern) + " ESCAPE '#'"
+ + " OR inputs LIKE ?" +
setParameter(parameters, pattern) + " ESCAPE '#'"
+ + " OR output LIKE ?" +
setParameter(parameters, pattern) + " ESCAPE '#'"
+ + " OR throwable LIKE ?" +
setParameter(parameters, pattern) + " ESCAPE '#')";
+ }).collect(Collectors.joining(" OR "))).
+ append(")");
+ }
+ return this;
+ }
+
public AuditEventCriteriaBuilder who(final Set<String> who, final
List<Object> parameters) {
if (!CollectionUtils.isEmpty(who)) {
query.append(andIfNeeded()).append("who IN (").
@@ -140,6 +165,7 @@ public class JPAAuditEventDAO extends AbstractAuditEventDAO
implements AuditEven
@Override
public long count(
final String entityKey,
+ final Set<String> username,
final Set<String> who,
final OpEvent.CategoryType type,
final String category,
@@ -153,6 +179,7 @@ public class JPAAuditEventDAO extends AbstractAuditEventDAO
implements AuditEven
String queryString = "SELECT COUNT(0)"
+ " FROM " + JPAAuditEvent.TABLE
+ " WHERE" + criteriaBuilder(entityKey).
+ username(username, parameters).
who(who, parameters).
opEvent(type, category, subcategory, op, outcome).
before(before, parameters).
@@ -168,6 +195,7 @@ public class JPAAuditEventDAO extends AbstractAuditEventDAO
implements AuditEven
@Override
public List<AuditEventTO> search(
final String entityKey,
+ final Set<String> username,
final Set<String> who,
final OpEvent.CategoryType type,
final String category,
@@ -182,6 +210,7 @@ public class JPAAuditEventDAO extends AbstractAuditEventDAO
implements AuditEven
String queryString = "SELECT id"
+ " FROM " + JPAAuditEvent.TABLE
+ " WHERE" + criteriaBuilder(entityKey).
+ username(username, parameters).
who(who, parameters).
opEvent(type, category, subcategory, op, outcome).
before(before, parameters).
diff --git
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAuditEventDAO.java
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAuditEventDAO.java
index 2c59bdbf0a..b976c3a162 100644
---
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAuditEventDAO.java
+++
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAuditEventDAO.java
@@ -60,6 +60,16 @@ public class Neo4jAuditEventDAO extends
AbstractAuditEventDAO implements AuditEv
return this;
}
+ public AuditEventCriteriaBuilder username(final Set<String> username,
final Map<String, Object> parameters) {
+ if (!CollectionUtils.isEmpty(username)) {
+ parameters.put("usernames", username.stream().map(value ->
"\"username\":\"" + value + "\"").toList());
+ query.append(andIfNeeded()).
+ append("ANY(u IN $usernames WHERE n.before CONTAINS u
OR n.inputs CONTAINS u "
+ + "OR n.output CONTAINS u OR n.throwable
CONTAINS u)");
+ }
+ return this;
+ }
+
public AuditEventCriteriaBuilder who(final Set<String> who, final
Map<String, Object> parameters) {
if (!CollectionUtils.isEmpty(who)) {
parameters.put("who", List.copyOf(who));
@@ -135,6 +145,7 @@ public class Neo4jAuditEventDAO extends
AbstractAuditEventDAO implements AuditEv
@Override
public long count(
final String entityKey,
+ final Set<String> username,
final Set<String> who,
final OpEvent.CategoryType type,
final String category,
@@ -147,6 +158,7 @@ public class Neo4jAuditEventDAO extends
AbstractAuditEventDAO implements AuditEv
Map<String, Object> parameters = new HashMap<>();
String query = "MATCH (n:" + Neo4jAuditEvent.NODE + ") "
+ " WHERE " + criteriaBuilder(entityKey).
+ username(username, parameters).
who(who, parameters).
opEvent(type, category, subcategory, op, outcome).
before(before, parameters).
@@ -160,6 +172,7 @@ public class Neo4jAuditEventDAO extends
AbstractAuditEventDAO implements AuditEv
@Override
public List<AuditEventTO> search(
final String entityKey,
+ final Set<String> username,
final Set<String> who,
final OpEvent.CategoryType type,
final String category,
@@ -174,6 +187,7 @@ public class Neo4jAuditEventDAO extends
AbstractAuditEventDAO implements AuditEv
StringBuilder query = new StringBuilder("MATCH (n:" +
Neo4jAuditEvent.NODE + ") "
+ "WHERE " + criteriaBuilder(entityKey).
+ username(username, parameters).
who(who, parameters).
opEvent(type, category, subcategory, op, outcome).
before(before, parameters).
diff --git
a/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAuditEventDAO.java
b/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAuditEventDAO.java
index 2798489ee5..de1c2307d7 100644
---
a/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAuditEventDAO.java
+++
b/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAuditEventDAO.java
@@ -82,6 +82,7 @@ public class ElasticsearchAuditEventDAO implements
AuditEventDAO {
protected Query getQuery(
final String entityKey,
+ final Set<String> username,
final Set<String> who,
final OpEvent.CategoryType type,
final String category,
@@ -101,6 +102,17 @@ public class ElasticsearchAuditEventDAO implements
AuditEventDAO {
query("\"key\":\"" + entityKey +
"\"").build()).build());
}
+ if (!CollectionUtils.isEmpty(username)) {
+ List<Query> usernameQueries = username.stream().map(value -> new
Query.Builder().
+ multiMatch(QueryBuilders.multiMatch().
+ fields("before", "inputs", "output", "throwable").
+ type(TextQueryType.Phrase).
+ query("\"username\":\"" + value +
"\"").build()).build()).
+ toList();
+ queries.add(new Query.Builder().
+
bool(QueryBuilders.bool().should(usernameQueries).minimumShouldMatch("1").build()).build());
+ }
+
if (!CollectionUtils.isEmpty(who)) {
List<Query> whoQueries = who.stream().map(value -> new
Query.Builder().
term(QueryBuilders.term().field("who").value(value).build()).build()).
@@ -137,6 +149,7 @@ public class ElasticsearchAuditEventDAO implements
AuditEventDAO {
@Override
public long count(
final String entityKey,
+ final Set<String> username,
final Set<String> who,
final OpEvent.CategoryType type,
final String category,
@@ -148,7 +161,7 @@ public class ElasticsearchAuditEventDAO implements
AuditEventDAO {
CountRequest request = new CountRequest.Builder().
index(ElasticsearchUtils.getAuditIndex(AuthContextUtils.getDomain())).
- query(getQuery(entityKey, who, type, category, subcategory,
op, outcome, before, after)).
+ query(getQuery(entityKey, username, who, type, category,
subcategory, op, outcome, before, after)).
build();
LOG.debug("Count request: {}", request);
@@ -172,6 +185,7 @@ public class ElasticsearchAuditEventDAO implements
AuditEventDAO {
@Override
public List<AuditEventTO> search(
final String entityKey,
+ final Set<String> username,
final Set<String> who,
final OpEvent.CategoryType type,
final String category,
@@ -185,7 +199,7 @@ public class ElasticsearchAuditEventDAO implements
AuditEventDAO {
SearchRequest request = new SearchRequest.Builder().
index(ElasticsearchUtils.getAuditIndex(AuthContextUtils.getDomain())).
searchType(SearchType.QueryThenFetch).
- query(getQuery(entityKey, who, type, category, subcategory,
op, outcome, before, after)).
+ query(getQuery(entityKey, username, who, type, category,
subcategory, op, outcome, before, after)).
from(pageable.isUnpaged() ? 0 : pageable.getPageSize() *
pageable.getPageNumber()).
size(pageable.isUnpaged() ? indexMaxResultWindow :
pageable.getPageSize()).
sort(sortBuilders(pageable.getSort().get())).
diff --git
a/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAuditEventDAO.java
b/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAuditEventDAO.java
index 02bf3be892..eb5d63feaf 100644
---
a/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAuditEventDAO.java
+++
b/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAuditEventDAO.java
@@ -81,6 +81,7 @@ public class OpenSearchAuditEventDAO implements AuditEventDAO
{
protected Query getQuery(
final String entityKey,
+ final Set<String> username,
final Set<String> who,
final OpEvent.CategoryType type,
final String category,
@@ -100,6 +101,17 @@ public class OpenSearchAuditEventDAO implements
AuditEventDAO {
query("\"key\":\"" + entityKey +
"\"").build()).build());
}
+ if (!CollectionUtils.isEmpty(username)) {
+ List<Query> usernameQueries = username.stream().map(value -> new
Query.Builder().
+ multiMatch(QueryBuilders.multiMatch().
+ fields("before", "inputs", "output", "throwable").
+ type(TextQueryType.Phrase).
+ query("\"username\":\"" + value +
"\"").build()).build()).
+ toList();
+ queries.add(new Query.Builder().
+
bool(QueryBuilders.bool().should(usernameQueries).minimumShouldMatch("1").build()).build());
+ }
+
if (!CollectionUtils.isEmpty(who)) {
List<Query> whoQueries = who.stream().map(value -> new
Query.Builder().
term(QueryBuilders.term().field("who").value(v ->
v.stringValue(value)).build()).build()).
@@ -136,6 +148,7 @@ public class OpenSearchAuditEventDAO implements
AuditEventDAO {
@Override
public long count(
final String entityKey,
+ final Set<String> username,
final Set<String> who,
final OpEvent.CategoryType type,
final String category,
@@ -147,7 +160,7 @@ public class OpenSearchAuditEventDAO implements
AuditEventDAO {
CountRequest request = new CountRequest.Builder().
index(OpenSearchUtils.getAuditIndex(AuthContextUtils.getDomain())).
- query(getQuery(entityKey, who, type, category, subcategory,
op, outcome, before, after)).
+ query(getQuery(entityKey, username, who, type, category,
subcategory, op, outcome, before, after)).
build();
LOG.debug("Count request: {}", request);
@@ -171,6 +184,7 @@ public class OpenSearchAuditEventDAO implements
AuditEventDAO {
@Override
public List<AuditEventTO> search(
final String entityKey,
+ final Set<String> username,
final Set<String> who,
final OpEvent.CategoryType type,
final String category,
@@ -184,7 +198,7 @@ public class OpenSearchAuditEventDAO implements
AuditEventDAO {
SearchRequest request = new SearchRequest.Builder().
index(OpenSearchUtils.getAuditIndex(AuthContextUtils.getDomain())).
searchType(SearchType.QueryThenFetch).
- query(getQuery(entityKey, who, type, category, subcategory,
op, outcome, before, after)).
+ query(getQuery(entityKey, username, who, type, category,
subcategory, op, outcome, before, after)).
from(pageable.isUnpaged() ? 0 : pageable.getPageSize() *
pageable.getPageNumber()).
size(pageable.isUnpaged() ? indexMaxResultWindow :
pageable.getPageSize()).
sort(sortBuilders(pageable.getSort().get())).
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 084914a9e2..d7565a51d9 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
@@ -234,6 +234,87 @@ public class AuditITCase extends AbstractITCase {
USER_SERVICE.delete(userTO.getKey());
}
+ @Test
+ public void findByUsername() {
+ UserTO userTO =
createUser(UserITCase.getUniqueSample("[email protected]")).getEntity();
+ assertNotNull(userTO.getKey());
+
+ // exact match on the username whose payload was logged for the create
event
+ AuditQuery exact = new AuditQuery.Builder().
+ username(userTO.getUsername()).
+ type(OpEvent.CategoryType.LOGIC).
+ category(UserLogic.class.getSimpleName()).
+ op("create").
+ outcome(OpEvent.Outcome.SUCCESS).
+ build();
+ List<AuditEventTO> exactResult = query(exact, MAX_WAIT_SECONDS);
+ assertFalse(exactResult.isEmpty());
+ // the matched event genuinely carries this username in its payload
(not matched by chance)
+ assertTrue(exactResult.stream().anyMatch(event -> (event.getOutput()
!= null
+ && event.getOutput().contains("\"username\":\"" +
userTO.getUsername() + "\""))
+ || (event.getBefore() != null
+ && event.getBefore().contains("\"username\":\"" +
userTO.getUsername() + "\""))));
+
+ // multiple username values are OR'ed
+ AuditQuery multiple = new AuditQuery.Builder().
+ username(userTO.getUsername()).
+ username("non-existent-" + UUID.randomUUID()).
+ type(OpEvent.CategoryType.LOGIC).
+ category(UserLogic.class.getSimpleName()).
+ op("create").
+ outcome(OpEvent.Outcome.SUCCESS).
+ build();
+ assertFalse(AUDIT_SERVICE.search(multiple).getResult().isEmpty());
+
+ // a non-matching username excludes the event
+ AuditQuery noMatch = new AuditQuery.Builder().
+ username("non-existent-" + UUID.randomUUID()).
+ type(OpEvent.CategoryType.LOGIC).
+ category(UserLogic.class.getSimpleName()).
+ op("create").
+ outcome(OpEvent.Outcome.SUCCESS).
+ build();
+ assertTrue(AUDIT_SERVICE.search(noMatch).getResult().isEmpty());
+
+ // the match is exact: a strict prefix of the username does not match
+ AuditQuery prefix = new AuditQuery.Builder().
+ username(userTO.getUsername().substring(0,
userTO.getUsername().length() - 1)).
+ type(OpEvent.CategoryType.LOGIC).
+ category(UserLogic.class.getSimpleName()).
+ op("create").
+ outcome(OpEvent.Outcome.SUCCESS).
+ build();
+ assertTrue(AUDIT_SERVICE.search(prefix).getResult().isEmpty());
+
+ USER_SERVICE.delete(userTO.getKey());
+ }
+
+ @Test
+ public void findDeletedUserByUsername() {
+ UserTO userTO =
createUser(UserITCase.getUniqueSample("[email protected]")).getEntity();
+ String username = userTO.getUsername();
+ assertNotNull(userTO.getKey());
+
+ AuditQuery deleteQuery = new AuditQuery.Builder().
+ username(username).
+ type(OpEvent.CategoryType.LOGIC).
+ category(UserLogic.class.getSimpleName()).
+ op("delete").
+ outcome(OpEvent.Outcome.SUCCESS).
+ build();
+ try {
+
AUDIT_SERVICE.setConf(buildAuditConf("[LOGIC]:[UserLogic]:[]:[delete]:[SUCCESS]",
true));
+
+ USER_SERVICE.delete(userTO.getKey());
+
+ // the user is gone (its key can no longer be resolved), but the
delete event's "before"
+ // snapshot still carries the username, so it remains searchable
by username
+ assertFalse(query(deleteQuery, MAX_WAIT_SECONDS).isEmpty());
+ } finally {
+
AUDIT_SERVICE.setConf(buildAuditConf("[LOGIC]:[UserLogic]:[]:[delete]:[SUCCESS]",
false));
+ }
+ }
+
@Test
public void findByGroup() {
GroupTO groupTO =
createGroup(GroupITCase.getBasicSample("AuditGroup")).getEntity();