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 2800cb3c82 [SYNCOPE-1949] Allow to search AuthProfile owners (#1296)
2800cb3c82 is described below

commit 2800cb3c82b42ee7dc613b0759038e899ddcdee2
Author: mrcx5 <[email protected]>
AuthorDate: Mon Feb 16 12:20:41 2026 +0100

    [SYNCOPE-1949] Allow to search AuthProfile owners (#1296)
---
 .../authprofiles/AuthProfileDirectoryPanel.java    | 29 ++++++++-
 .../console/authprofiles/AuthProfilePanel.java     | 70 ++++++++++++++++++++++
 .../apache/syncope/client/console/pages/WA.java    |  4 +-
 .../client/console/rest/AuthProfileRestClient.java | 13 ++--
 .../console/authprofiles/AuthProfilePanel.html     | 36 +++++++++++
 .../common/rest/api/beans/AuthProfileQuery.java    | 55 +++++++++++++++++
 .../rest/api/service/AuthProfileService.java       | 16 ++---
 .../syncope/core/logic/AuthProfileLogic.java       |  8 +--
 .../rest/cxf/service/AuthProfileServiceImpl.java   |  7 ++-
 .../core/persistence/api/dao/AuthProfileDAO.java   |  5 ++
 .../persistence/jpa/inner/AuthProfileTest.java     | 13 ++++
 .../core/persistence/neo4j/PersistenceContext.java | 18 +++++-
 .../neo4j/dao/repo/AuthProfileRepo.java            |  2 +-
 .../neo4j/dao/repo/AuthProfileRepoExt.java}        | 11 ++--
 .../neo4j/dao/repo/AuthProfileRepoExtImpl.java     | 65 ++++++++++++++++++++
 .../persistence/neo4j/inner/AuthProfileTest.java   | 12 ++++
 .../fit/core/wa/GoogleMfaAuthTokenITCase.java      | 56 +++++++++++++----
 17 files changed, 373 insertions(+), 47 deletions(-)

diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileDirectoryPanel.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileDirectoryPanel.java
index ed955350ac..be7597329e 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileDirectoryPanel.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileDirectoryPanel.java
@@ -24,10 +24,13 @@ import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Strings;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import 
org.apache.syncope.client.console.authprofiles.AuthProfileDirectoryPanel.AuthProfileProvider;
 import org.apache.syncope.client.console.commons.AMConstants;
 import org.apache.syncope.client.console.commons.DirectoryDataProvider;
+import org.apache.syncope.client.console.commons.KeywordSearchEvent;
 import org.apache.syncope.client.console.panels.DirectoryPanel;
 import org.apache.syncope.client.console.panels.ModalDirectoryPanel;
 import org.apache.syncope.client.console.rest.AuthProfileRestClient;
@@ -48,6 +51,7 @@ import org.apache.syncope.common.lib.wa.MfaTrustedDevice;
 import org.apache.syncope.common.lib.wa.WebAuthnDeviceCredential;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.event.IEvent;
 import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import 
org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
@@ -62,6 +66,8 @@ public class AuthProfileDirectoryPanel
 
     private static final long serialVersionUID = 2018518567549153364L;
 
+    private String keyword;
+
     private final BaseModal<AuthProfileTO> authProfileModal;
 
     public AuthProfileDirectoryPanel(
@@ -431,6 +437,25 @@ public class AuthProfileDirectoryPanel
         return panel;
     }
 
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof KeywordSearchEvent payload) {
+            keyword = payload.getKeyword();
+            if (StringUtils.isNotBlank(keyword)) {
+                if (!Strings.CS.startsWith(keyword, "*")) {
+                    keyword = "*" + keyword;
+                }
+                if (!Strings.CS.endsWith(keyword, "*")) {
+                    keyword += "*";
+                }
+            }
+
+            updateResultTable(payload.getTarget());
+        } else {
+            super.onEvent(event);
+        }
+    }
+
     protected final class AuthProfileProvider extends 
DirectoryDataProvider<AuthProfileTO> {
 
         private static final long serialVersionUID = -185944053385660794L;
@@ -443,12 +468,12 @@ public class AuthProfileDirectoryPanel
         @Override
         public Iterator<AuthProfileTO> iterator(final long first, final long 
count) {
             int page = ((int) first / paginatorRows);
-            return restClient.list((page < 0 ? 0 : page) + 1, 
paginatorRows).iterator();
+            return restClient.search(keyword, (page < 0 ? 0 : page) + 1, 
paginatorRows).iterator();
         }
 
         @Override
         public long size() {
-            return restClient.count();
+            return restClient.count(keyword);
         }
 
         @Override
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfilePanel.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfilePanel.java
new file mode 100644
index 0000000000..f880f64cac
--- /dev/null
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfilePanel.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.client.console.authprofiles;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.commons.KeywordSearchEvent;
+import org.apache.syncope.client.console.rest.AuthProfileRestClient;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
+
+
+public class AuthProfilePanel extends Panel {
+
+    private static final long serialVersionUID = -4716856239434102405L;
+
+    public AuthProfilePanel(final String id,
+                            final AuthProfileRestClient authProfileRestClient,
+                            final PageReference pageRef) {
+        super(id);
+
+        Model<String> keywordModel = new Model<>(StringUtils.EMPTY);
+
+        WebMarkupContainer searchBoxContainer = new 
WebMarkupContainer("searchBox");
+        add(searchBoxContainer);
+
+        Form<?> form = new Form<>("form");
+        searchBoxContainer.add(form);
+
+        AjaxTextFieldPanel filter = new AjaxTextFieldPanel("filter", "filter", 
keywordModel, true);
+        
form.add(filter.hideLabel().setOutputMarkupId(true).setRenderBodyOnly(true));
+
+        AjaxButton search = new AjaxButton("search") {
+
+            private static final long serialVersionUID = 8390605330558248736L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target) {
+                send(AuthProfilePanel.this, Broadcast.DEPTH, new 
KeywordSearchEvent(target, keywordModel.getObject()));
+            }
+        };
+        search.setOutputMarkupId(true);
+        form.add(search);
+        form.setDefaultButton(search);
+
+        add(new AuthProfileDirectoryPanel("authProfiles", 
authProfileRestClient, pageRef).setOutputMarkupId(true));
+    }
+}
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java
index f6a1003ccd..e37d5a2cb9 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java
@@ -34,7 +34,7 @@ import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.SyncopeWebApplication;
-import 
org.apache.syncope.client.console.authprofiles.AuthProfileDirectoryPanel;
+import org.apache.syncope.client.console.authprofiles.AuthProfilePanel;
 import org.apache.syncope.client.console.clientapps.ClientApps;
 import org.apache.syncope.client.console.panels.AMSessionPanel;
 import org.apache.syncope.client.console.panels.AttrRepoDirectoryPanel;
@@ -264,7 +264,7 @@ public class WA extends BasePage {
 
                 @Override
                 public Panel getPanel(final String panelId) {
-                    return new AuthProfileDirectoryPanel(panelId, 
authProfileRestClient, getPageReference());
+                    return new AuthProfilePanel(panelId, 
authProfileRestClient, getPageReference());
                 }
             });
         }
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/rest/AuthProfileRestClient.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/rest/AuthProfileRestClient.java
index d6801145ed..99f8cb18ce 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/rest/AuthProfileRestClient.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/rest/AuthProfileRestClient.java
@@ -20,18 +20,23 @@ package org.apache.syncope.client.console.rest;
 
 import java.util.List;
 import org.apache.syncope.common.lib.to.AuthProfileTO;
+import org.apache.syncope.common.rest.api.beans.AuthProfileQuery;
 import org.apache.syncope.common.rest.api.service.AuthProfileService;
 
 public class AuthProfileRestClient extends BaseRestClient {
 
     private static final long serialVersionUID = -7379778542101161274L;
 
-    public long count() {
-        return getService(AuthProfileService.class).list(1, 1).getTotalCount();
+    public long count(final String keyword) {
+        return getService(AuthProfileService.class).
+                search(new 
AuthProfileQuery.Builder().page(1).size(0).keyword(keyword).build()).
+                getTotalCount();
     }
 
-    public List<AuthProfileTO> list(final int page, final int size) {
-        return getService(AuthProfileService.class).list(page, 
size).getResult();
+    public List<AuthProfileTO> search(final String keyword, final int page, 
final int size) {
+        return getService(AuthProfileService.class).
+                search(new 
AuthProfileQuery.Builder().page(page).size(size).keyword(keyword).build()).
+                getResult();
     }
 
     public AuthProfileTO read(final String key) {
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/authprofiles/AuthProfilePanel.html
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/authprofiles/AuthProfilePanel.html
new file mode 100644
index 0000000000..8d2a5af99e
--- /dev/null
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/authprofiles/AuthProfilePanel.html
@@ -0,0 +1,36 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"; 
xmlns:wicket="http://wicket.apache.org";>
+  <wicket:panel>
+    <div wicket:id="searchBox">
+      <form wicket:id="form">
+        <div class="input-group mb-3">
+          <span wicket:id="filter">[FILTER]</span>
+          <span class="input-group-btn">
+            <button type="button" class="btn btn-default btn-flat" 
wicket:id="search">
+              <span class="fas fa-search" aria-hidden="true"></span>
+            </button>
+          </span>
+        </div>
+      </form>
+    </div>
+
+    <div wicket:id="authProfiles"></div>
+  </wicket:panel>
+</html>
diff --git 
a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuthProfileQuery.java
 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuthProfileQuery.java
new file mode 100644
index 0000000000..9b43bdb237
--- /dev/null
+++ 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuthProfileQuery.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.common.rest.api.beans;
+
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.ws.rs.QueryParam;
+import org.apache.syncope.common.rest.api.service.JAXRSService;
+
+public class AuthProfileQuery extends AbstractQuery {
+
+    private static final long serialVersionUID = -466451202500478654L;
+
+    public static class Builder extends 
AbstractQuery.Builder<AuthProfileQuery, Builder> {
+
+        @Override
+        protected AuthProfileQuery newInstance() {
+            return new AuthProfileQuery();
+        }
+
+        public Builder keyword(final String keyword) {
+            getInstance().setKeyword(keyword);
+            return this;
+        }
+    }
+
+    private String keyword;
+
+    @Parameter(name = JAXRSService.PARAM_KEYWORD, description = "keyword to 
match", schema =
+            @Schema(implementation = String.class))
+    public String getKeyword() {
+        return keyword;
+    }
+
+    @QueryParam(JAXRSService.PARAM_KEYWORD)
+    public void setKeyword(final String keyword) {
+        this.keyword = keyword;
+    }
+}
diff --git 
a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthProfileService.java
 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthProfileService.java
index 81ba99188c..ed4073529f 100644
--- 
a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthProfileService.java
+++ 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthProfileService.java
@@ -27,24 +27,23 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
 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 jakarta.validation.constraints.Min;
 import jakarta.validation.constraints.NotNull;
+import jakarta.ws.rs.BeanParam;
 import jakarta.ws.rs.Consumes;
 import jakarta.ws.rs.DELETE;
-import jakarta.ws.rs.DefaultValue;
 import jakarta.ws.rs.GET;
 import jakarta.ws.rs.POST;
 import jakarta.ws.rs.PUT;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.PathParam;
 import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
 import jakarta.ws.rs.core.HttpHeaders;
 import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.Response;
 import org.apache.syncope.common.lib.to.AuthProfileTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.beans.AuthProfileQuery;
 
 /**
  * REST operations for Auth profiles.
@@ -57,17 +56,14 @@ import org.apache.syncope.common.rest.api.RESTHeaders;
 public interface AuthProfileService extends JAXRSService {
 
     /**
-     * Returns the paginated list of existing auth profiles.
+     * Returns a paged list of all AuthProfiles.
      *
-     * @param page search page
-     * @param size search page size
-     * @return the paginated list of existing auth profiles
+     * @param query query conditions
+     * @return list of all AuthProfiles.
      */
     @GET
     @Produces({ MediaType.APPLICATION_JSON })
-    PagedResult<AuthProfileTO> list(
-            @Min(1) @QueryParam(PARAM_PAGE) @DefaultValue("1") int page,
-            @Min(1) @QueryParam(PARAM_SIZE) @DefaultValue("25") int size);
+    PagedResult<AuthProfileTO> search(@BeanParam AuthProfileQuery query);
 
     /**
      * Returns the auth profile matching the provided if key, if found.
diff --git 
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthProfileLogic.java
 
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthProfileLogic.java
index e13ccc1caf..a14c3eec84 100644
--- 
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthProfileLogic.java
+++ 
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthProfileLogic.java
@@ -48,12 +48,10 @@ public class AuthProfileLogic extends 
AbstractAuthProfileLogic {
 
     @PreAuthorize("hasRole('" + AMEntitlement.AUTH_PROFILE_LIST + "')")
     @Transactional(readOnly = true)
-    public Page<AuthProfileTO> list(final Pageable pageable) {
-        long count = authProfileDAO.count();
-
-        List<AuthProfileTO> result = authProfileDAO.findAll(pageable).
+    public Page<AuthProfileTO> search(final String keyword, final Pageable 
pageable) {
+        long count = authProfileDAO.countByOwnerLike(keyword);
+        List<AuthProfileTO> result = authProfileDAO.findByOwnerLike(keyword, 
pageable).
                 stream().map(binder::getAuthProfileTO).toList();
-
         return new SyncopePage<>(result, pageable, count);
     }
 
diff --git 
a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthProfileServiceImpl.java
 
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthProfileServiceImpl.java
index c19e418143..998abb7389 100644
--- 
a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthProfileServiceImpl.java
+++ 
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthProfileServiceImpl.java
@@ -23,10 +23,10 @@ import java.net.URI;
 import org.apache.syncope.common.lib.to.AuthProfileTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.beans.AuthProfileQuery;
 import org.apache.syncope.common.rest.api.service.AuthProfileService;
 import org.apache.syncope.core.logic.AuthProfileLogic;
 import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageRequest;
 
 public class AuthProfileServiceImpl extends AbstractService implements 
AuthProfileService {
 
@@ -37,8 +37,9 @@ public class AuthProfileServiceImpl extends AbstractService 
implements AuthProfi
     }
 
     @Override
-    public PagedResult<AuthProfileTO> list(final int page, final int size) {
-        Page<AuthProfileTO> result = logic.list(PageRequest.of(page < 1 ? 0 : 
page - 1, size < 1 ? 1 : size));
+    public PagedResult<AuthProfileTO> search(final AuthProfileQuery query) {
+        String keyword = query.getKeyword() == null ? "%" : 
query.getKeyword().replace('*', '%');
+        Page<AuthProfileTO> result = logic.search(keyword, pageable(query));
         return buildPagedResult(result);
     }
 
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuthProfileDAO.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuthProfileDAO.java
index 89b5c5d563..747bfe2051 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuthProfileDAO.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuthProfileDAO.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.persistence.api.dao;
 
+import java.util.List;
 import java.util.Optional;
 import org.apache.syncope.core.persistence.api.entity.am.AuthProfile;
 import org.springframework.data.domain.Page;
@@ -28,4 +29,8 @@ public interface AuthProfileDAO extends DAO<AuthProfile> {
     Optional<? extends AuthProfile> findByOwner(String owner);
 
     Page<? extends AuthProfile> findAll(Pageable pageable);
+
+    List<? extends AuthProfile> findByOwnerLike(String owner, Pageable 
pageable);
+
+    long countByOwnerLike(String owner);
 }
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthProfileTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthProfileTest.java
index 104d916e53..47ceea5b06 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthProfileTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthProfileTest.java
@@ -192,4 +192,17 @@ public class AuthProfileTest extends AbstractTest {
         profile.setGoogleMfaAuthAccounts(List.of(account));
         return authProfileDAO.save(profile);
     }
+
+    @Test
+    public void findByOwnerLike() {
+        createAuthProfileWithAccount("owner1");
+        createAuthProfileWithAccount("owner2");
+        createAuthProfileWithAccount("test");
+
+        entityManager.flush();
+
+        List<? extends AuthProfile> result = 
authProfileDAO.findByOwnerLike("owner%", Pageable.unpaged());
+        assertEquals(2, result.size());
+        assertEquals(2, authProfileDAO.countByOwnerLike("owner%"));
+    }
 }
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java
index cc65777e59..ad15dc85f7 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java
@@ -121,6 +121,8 @@ import 
org.apache.syncope.core.persistence.neo4j.dao.repo.AuthModuleRepo;
 import org.apache.syncope.core.persistence.neo4j.dao.repo.AuthModuleRepoExt;
 import 
org.apache.syncope.core.persistence.neo4j.dao.repo.AuthModuleRepoExtImpl;
 import org.apache.syncope.core.persistence.neo4j.dao.repo.AuthProfileRepo;
+import org.apache.syncope.core.persistence.neo4j.dao.repo.AuthProfileRepoExt;
+import 
org.apache.syncope.core.persistence.neo4j.dao.repo.AuthProfileRepoExtImpl;
 import org.apache.syncope.core.persistence.neo4j.dao.repo.CASSPClientAppRepo;
 import 
org.apache.syncope.core.persistence.neo4j.dao.repo.CASSPClientAppRepoExt;
 import 
org.apache.syncope.core.persistence.neo4j.dao.repo.CASSPClientAppRepoExtImpl;
@@ -700,8 +702,20 @@ public class PersistenceContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public AuthProfileDAO authProfileDAO(final SyncopeNeo4jRepositoryFactory 
neo4jRepositoryFactory) {
-        return neo4jRepositoryFactory.getRepository(AuthProfileRepo.class);
+    public AuthProfileRepoExt authProfileRepoExt(
+            final Neo4jTemplate neo4jTemplate,
+            final Neo4jClient neo4jClient) {
+
+        return new AuthProfileRepoExtImpl(neo4jTemplate, neo4jClient);
+    }
+
+    @ConditionalOnMissingBean
+    @Bean
+    public AuthProfileDAO authProfileDAO(
+            final SyncopeNeo4jRepositoryFactory neo4jRepositoryFactory,
+            final AuthProfileRepoExt authProfileRepoExt) {
+
+        return neo4jRepositoryFactory.getRepository(AuthProfileRepo.class, 
authProfileRepoExt);
     }
 
     @ConditionalOnMissingBean
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AuthProfileRepo.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AuthProfileRepo.java
index 86ccb54bed..e70ec8ebce 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AuthProfileRepo.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AuthProfileRepo.java
@@ -23,6 +23,6 @@ import 
org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jAuthProfile;
 import org.springframework.data.repository.PagingAndSortingRepository;
 
 public interface AuthProfileRepo
-        extends PagingAndSortingRepository<Neo4jAuthProfile, String>, 
AuthProfileDAO {
+        extends PagingAndSortingRepository<Neo4jAuthProfile, String>, 
AuthProfileRepoExt, AuthProfileDAO {
 
 }
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuthProfileDAO.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AuthProfileRepoExt.java
similarity index 75%
copy from 
core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuthProfileDAO.java
copy to 
core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AuthProfileRepoExt.java
index 89b5c5d563..77c3ae371c 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuthProfileDAO.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AuthProfileRepoExt.java
@@ -16,16 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.persistence.api.dao;
+package org.apache.syncope.core.persistence.neo4j.dao.repo;
 
-import java.util.Optional;
+import java.util.List;
 import org.apache.syncope.core.persistence.api.entity.am.AuthProfile;
-import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 
-public interface AuthProfileDAO extends DAO<AuthProfile> {
+public interface AuthProfileRepoExt {
 
-    Optional<? extends AuthProfile> findByOwner(String owner);
+    List<? extends AuthProfile> findByOwnerLike(String owner, Pageable 
pageable);
 
-    Page<? extends AuthProfile> findAll(Pageable pageable);
+    long countByOwnerLike(String owner);
 }
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AuthProfileRepoExtImpl.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AuthProfileRepoExtImpl.java
new file mode 100644
index 0000000000..3b3ea1904d
--- /dev/null
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AuthProfileRepoExtImpl.java
@@ -0,0 +1,65 @@
+/*
+ * 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.neo4j.dao.repo;
+
+import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.am.AuthProfile;
+import org.apache.syncope.core.persistence.neo4j.dao.AbstractDAO;
+import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jAuthProfile;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.neo4j.core.Neo4jClient;
+import org.springframework.data.neo4j.core.Neo4jTemplate;
+
+
+public class AuthProfileRepoExtImpl extends AbstractDAO implements 
AuthProfileRepoExt {
+
+    public AuthProfileRepoExtImpl(
+            final Neo4jTemplate neo4jTemplate,
+            final Neo4jClient neo4jClient) {
+
+        super(neo4jTemplate, neo4jClient);
+    }
+
+    protected StringBuilder query(final String owner) {
+        return  new StringBuilder(
+                "MATCH (n:").append(Neo4jAuthProfile.NODE).append(") WHERE ").
+                append("n.owner =~ 
\"").append(AnyRepoExt.escapeForLikeRegex(owner).replace("%", ".*")).
+                append('"');
+    }
+
+    @Override
+    public List<? extends AuthProfile> findByOwnerLike(final String owner, 
final Pageable pageable) {
+        StringBuilder query = query(owner).append(" RETURN n.id");
+
+        if (pageable.isPaged()) {
+            query.append(" SKIP ").append(pageable.getPageSize() * 
pageable.getPageNumber()).
+                    append(" LIMIT ").append(pageable.getPageSize());
+        }
+
+        return toList(neo4jClient.query(query.toString()).fetch().all(), 
"n.id", Neo4jAuthProfile.class, null);
+    }
+
+    @Override
+    public long countByOwnerLike(final String owner) {
+        StringBuilder query = query(owner).append(" RETURN COUNT(n.id)");
+
+        return neo4jTemplate.count(query.toString());
+    }
+
+}
diff --git 
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/AuthProfileTest.java
 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/AuthProfileTest.java
index 659a6cd81e..6286085f6b 100644
--- 
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/AuthProfileTest.java
+++ 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/AuthProfileTest.java
@@ -40,6 +40,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.neo4j.core.Neo4jTemplate;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -196,4 +197,15 @@ public class AuthProfileTest extends AbstractTest {
         profile.setGoogleMfaAuthAccounts(List.of(account));
         return authProfileDAO.save(profile);
     }
+
+    @Test
+    public void findByOwnerLike() {
+        createAuthProfileWithAccount("owner1");
+        createAuthProfileWithAccount("owner2");
+        createAuthProfileWithAccount("test");
+
+        List<? extends AuthProfile> result = 
authProfileDAO.findByOwnerLike("owner%", Pageable.unpaged());
+        assertEquals(2, result.size());
+        assertEquals(2, authProfileDAO.countByOwnerLike("owner%"));
+    }
 }
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/wa/GoogleMfaAuthTokenITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/wa/GoogleMfaAuthTokenITCase.java
index 759301ddb5..2a3d35ee97 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/wa/GoogleMfaAuthTokenITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/wa/GoogleMfaAuthTokenITCase.java
@@ -20,7 +20,6 @@ package org.apache.syncope.fit.core.wa;
 
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -31,6 +30,7 @@ import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.AuthProfileTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.wa.GoogleMfaAuthToken;
+import org.apache.syncope.common.rest.api.beans.AuthProfileQuery;
 import org.apache.syncope.fit.AbstractITCase;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -65,17 +65,49 @@ public class GoogleMfaAuthTokenITCase extends 
AbstractITCase {
     }
 
     @Test
-    public void verifyProfile() {
-        String owner = UUID.randomUUID().toString();
-        GoogleMfaAuthToken token = createGoogleMfaAuthToken();
-        GOOGLE_MFA_AUTH_TOKEN_SERVICE.store(owner, token);
-        PagedResult<AuthProfileTO> results = AUTH_PROFILE_SERVICE.list(1, 100);
-        assertFalse(results.getResult().isEmpty());
-        AuthProfileTO profileTO = results.getResult().stream().
-                filter(p -> owner.equals(p.getOwner())).findFirst().get();
-        assertEquals(profileTO, AUTH_PROFILE_SERVICE.read(profileTO.getKey()));
-        AUTH_PROFILE_SERVICE.delete(profileTO.getKey());
-        assertThrows(SyncopeClientException.class, () -> 
AUTH_PROFILE_SERVICE.read(profileTO.getKey()));
+    public void listProfiles() {
+        String owner = "owner" + UUID.randomUUID();
+        String owner1 = "owner" + UUID.randomUUID();
+        String test = "test" + UUID.randomUUID();
+        PagedResult<AuthProfileTO> results = null;
+        PagedResult<AuthProfileTO> resultsTest = null;
+        try {
+            GoogleMfaAuthToken token = createGoogleMfaAuthToken();
+            GOOGLE_MFA_AUTH_TOKEN_SERVICE.store(owner, token);
+
+            GoogleMfaAuthToken token1 = createGoogleMfaAuthToken();
+            GOOGLE_MFA_AUTH_TOKEN_SERVICE.store(owner1, token1);
+
+            GoogleMfaAuthToken token2 = createGoogleMfaAuthToken();
+            GOOGLE_MFA_AUTH_TOKEN_SERVICE.store(test, token2);
+
+            results = AUTH_PROFILE_SERVICE.search(
+                    new 
AuthProfileQuery.Builder().page(1).size(100).keyword("owner*").build());
+
+            resultsTest = AUTH_PROFILE_SERVICE.search(
+                    new 
AuthProfileQuery.Builder().page(1).size(100).keyword("test*").build());
+
+            assertEquals(2, results.getTotalCount());
+            assertEquals(2, results.getResult().size());
+        } finally {
+            AuthProfileTO profileTO = results.getResult().stream().
+                    filter(p -> 
owner.equals(p.getOwner())).findFirst().orElseThrow();
+            assertEquals(profileTO, 
AUTH_PROFILE_SERVICE.read(profileTO.getKey()));
+            AUTH_PROFILE_SERVICE.delete(profileTO.getKey());
+            assertThrows(SyncopeClientException.class, () -> 
AUTH_PROFILE_SERVICE.read(profileTO.getKey()));
+
+            AuthProfileTO profileTO1 = results.getResult().stream().
+                    filter(p -> 
owner1.equals(p.getOwner())).findFirst().orElseThrow();
+            assertEquals(profileTO1, 
AUTH_PROFILE_SERVICE.read(profileTO1.getKey()));
+            AUTH_PROFILE_SERVICE.delete(profileTO1.getKey());
+            assertThrows(SyncopeClientException.class, () -> 
AUTH_PROFILE_SERVICE.read(profileTO1.getKey()));
+
+            AuthProfileTO profileTO2 = resultsTest.getResult().stream().
+                    filter(p -> 
test.equals(p.getOwner())).findFirst().orElseThrow();
+            assertEquals(profileTO2, 
AUTH_PROFILE_SERVICE.read(profileTO2.getKey()));
+            AUTH_PROFILE_SERVICE.delete(profileTO2.getKey());
+            assertThrows(SyncopeClientException.class, () -> 
AUTH_PROFILE_SERVICE.read(profileTO2.getKey()));
+        }
     }
 
     @Test


Reply via email to