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

mmoayyed 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 d96d08f  SYNCOPE-1580: Manage WA Configuration Properties/Schema (#207)
d96d08f is described below

commit d96d08f128c98fb4b6ccc4aec6c23e4cf83fbcc3
Author: Misagh Moayyed <[email protected]>
AuthorDate: Mon Jul 27 16:55:09 2020 +0400

    SYNCOPE-1580: Manage WA Configuration Properties/Schema (#207)
---
 .../syncope/common/lib/types/AMEntitlement.java    |  12 +
 .../rest/api/service/wa/WAConfigService.java       | 101 ++++++++
 .../apache/syncope/core/logic/WAConfigLogic.java   | 161 +++++++++++++
 .../rest/cxf/service/wa/WAConfigServiceImpl.java   |  74 ++++++
 .../core/persistence/api/dao/auth/WAConfigDAO.java |  40 ++++
 .../persistence/api/entity/auth/WAConfigEntry.java |  30 +++
 .../persistence/jpa/dao/auth/JPAWAConfigDAO.java   |  74 ++++++
 .../persistence/jpa/entity/JPAEntityFactory.java   |   4 +
 .../jpa/entity/auth/JPAWAConfigEntry.java          |  54 +++++
 .../core/persistence/jpa/inner/WAConfigTest.java   | 100 ++++++++
 .../provisioning/api/data/WAConfigDataBinder.java  |  31 +++
 .../java/data/WAConfigDataBinderImpl.java          |  61 +++++
 .../org/apache/syncope/fit/AbstractITCase.java     |   4 +
 .../apache/syncope/fit/core/WAConfigITCase.java    |  74 ++++++
 .../bootstrap/SyncopeWAPropertySourceLocator.java  | 261 ++++++++++++++-------
 .../wa/starter/SyncopeCoreTestingServer.java       |  50 ++--
 16 files changed, 1022 insertions(+), 109 deletions(-)

diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
index d32468b..2be85c9 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
@@ -118,6 +118,18 @@ public final class AMEntitlement {
 
     public static final String U2F_UPDATE_DEVICE = "U2F_UPDATE_DEVICE";
 
+    public static final String WA_CONFIG_LIST = "WA_CONFIG_LIST";
+
+    public static final String WA_CONFIG_UPDATE = "WA_CONFIG_UPDATE";
+
+    public static final String WA_CONFIG_DELETE = "WA_CONFIG_DELETE";
+
+    public static final String WA_CONFIG_READ = "WA_CONFIG_READ";
+
+    public static final String WA_CONFIG_CREATE = "WA_CONFIG_CREATE";
+
+    public static final String WA_CONFIG_PUSH = "WA_CONFIG_PUSH";
+
     private static final Set<String> VALUES;
 
     static {
diff --git 
a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/WAConfigService.java
 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/WAConfigService.java
new file mode 100644
index 0000000..82fa78c
--- /dev/null
+++ 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/WAConfigService.java
@@ -0,0 +1,101 @@
+/*
+ * 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.wa;
+
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+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 org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.JAXRSService;
+
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import java.util.List;
+
+/**
+ * REST operations for WA Configuration.
+ */
+@Tag(name = "WA")
+@SecurityRequirements({
+    @SecurityRequirement(name = "BasicAuthentication"),
+    @SecurityRequirement(name = "Bearer")})
+@Path("wa/config")
+public interface WAConfigService extends JAXRSService {
+
+    @GET
+    @Consumes({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML})
+    @Produces({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML})
+    List<Attr> list();
+
+    @GET
+    @Path("{key}")
+    @Consumes({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML})
+    @Produces({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML})
+    Attr read(@NotNull @PathParam("key") String key);
+
+    @ApiResponses({
+        @ApiResponse(responseCode = "201",
+            description = "WAConfigTO successfully created", headers = {
+            @Header(name = RESTHeaders.RESOURCE_KEY, schema =
+            @Schema(type = "string"),
+                description = "UUID generated for the entity created"),
+            @Header(name = HttpHeaders.LOCATION, schema =
+            @Schema(type = "string"),
+                description = "URL of the entity created")}),
+        @ApiResponse(responseCode = "409",
+            description = "Config already existing")})
+    @POST
+    @Consumes({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML})
+    @Produces({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML})
+    Response create(@NotNull Attr configTO);
+    
+    @PUT
+    @Consumes({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML})
+    @Produces({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML})
+    void update(Attr configTO);
+
+    @DELETE
+    @Consumes({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML})
+    @Produces({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML})
+    @Path("{key}")
+    void delete(@NotNull @PathParam("key") String key);
+
+    @ApiResponses(
+        @ApiResponse(responseCode = "204", description = "Operation was 
successful"))
+    @POST
+    @Path("push")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    void pushToWA();
+}
diff --git 
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/WAConfigLogic.java 
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/WAConfigLogic.java
new file mode 100644
index 0000000..f3158de
--- /dev/null
+++ 
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/WAConfigLogic.java
@@ -0,0 +1,161 @@
+/*
+ * 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 org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.cxf.transport.http.auth.DefaultBasicAuthSupplier;
+import org.apache.syncope.common.keymaster.client.api.KeymasterException;
+import org.apache.syncope.common.keymaster.client.api.ServiceOps;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.lib.to.EntityTO;
+import org.apache.syncope.common.lib.types.AMEntitlement;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.auth.WAConfigDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.WAConfigEntry;
+import org.apache.syncope.core.provisioning.api.data.WAConfigDataBinder;
+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;
+
+import javax.annotation.Resource;
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.core.HttpHeaders;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Component
+public class WAConfigLogic extends AbstractTransactionalLogic<EntityTO> {
+    @Autowired
+    private ServiceOps serviceOps;
+
+    @Autowired
+    private WAConfigDataBinder binder;
+
+    @Autowired
+    private WAConfigDAO configDAO;
+
+    @Resource(name = "anonymousUser")
+    private String anonymousUser;
+
+    @Resource(name = "anonymousKey")
+    private String anonymousKey;
+
+    @Override
+    protected EntityTO resolveReference(final Method method, final Object... 
args)
+        throws UnresolvedReferenceException {
+        String key = null;
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; key == null && i < args.length; i++) {
+                if (args[i] instanceof String) {
+                    key = (String) args[i];
+                } else if (args[i] instanceof Attr) {
+                    key = ((Attr) args[i]).getSchema();
+                }
+            }
+        }
+
+        if (key != null) {
+            try {
+                Attr attr = binder.getAttr(configDAO.find(key));
+                return new EntityTO() {
+                    private static final long serialVersionUID = 
-2683326649597260323L;
+                    @Override
+                    public String getKey() {
+                        return attr.getSchema();
+                    }
+
+                    @Override
+                    public void setKey(final String key) {
+                    }
+                };
+            } catch (final Throwable e) {
+                LOG.debug("Unresolved reference", e);
+                throw new UnresolvedReferenceException(e);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.WA_CONFIG_LIST + "') or 
hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    @Transactional(readOnly = true)
+    public List<Attr> list() {
+        return 
configDAO.findAll().stream().map(binder::getAttr).collect(Collectors.toList());
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.WA_CONFIG_UPDATE + "')")
+    public void update(final Attr configTO) {
+        WAConfigEntry entry = configDAO.find(configTO.getSchema());
+        if (entry == null) {
+            throw new NotFoundException("Configuration entry " + 
configTO.getSchema() + " not found");
+        }
+        binder.update(entry, configTO);
+        configDAO.save(entry);
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.WA_CONFIG_DELETE + "')")
+    public void delete(final String key) {
+        configDAO.delete(key);
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.WA_CONFIG_READ + "') or 
hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    @Transactional(readOnly = true)
+    public Attr read(final String key) {
+        WAConfigEntry entry = configDAO.find(key);
+        if (entry == null) {
+            throw new NotFoundException("Configuration entry " + key + " not 
found");
+        }
+        return binder.getAttr(entry);
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.WA_CONFIG_CREATE + "')")
+    public Attr create(final Attr configTO) {
+        return binder.getAttr(configDAO.save(binder.create(configTO)));
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.WA_CONFIG_PUSH + "')")
+    public void pushToWA() {
+        try {
+            NetworkService wa = serviceOps.get(NetworkService.Type.WA);
+            HttpClient.newBuilder().build().send(
+                HttpRequest.newBuilder(URI.create(
+                    StringUtils.appendIfMissing(wa.getAddress(), "/") + 
"actuator/refresh")).
+                    header(HttpHeaders.AUTHORIZATION,
+                        
DefaultBasicAuthSupplier.getBasicAuthHeader(anonymousUser, anonymousKey)).
+                    POST(HttpRequest.BodyPublishers.noBody()).build(),
+                HttpResponse.BodyHandlers.discarding());
+        } catch (KeymasterException e) {
+            throw new NotFoundException("Could not find any WA instance", e);
+        } catch (IOException | InterruptedException e) {
+            throw new InternalServerErrorException("Errors while communicating 
with WA instance", e);
+        }
+    }
+}
diff --git 
a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/wa/WAConfigServiceImpl.java
 
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/wa/WAConfigServiceImpl.java
new file mode 100644
index 0000000..d8ad501
--- /dev/null
+++ 
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/wa/WAConfigServiceImpl.java
@@ -0,0 +1,74 @@
+/*
+ * 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.wa;
+
+import org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.wa.WAConfigService;
+import org.apache.syncope.core.logic.WAConfigLogic;
+import org.apache.syncope.core.rest.cxf.service.AbstractServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.ws.rs.core.Response;
+
+import java.net.URI;
+import java.util.List;
+
+@Service
+public class WAConfigServiceImpl extends AbstractServiceImpl implements 
WAConfigService {
+    @Autowired
+    private WAConfigLogic logic;
+
+    @Override
+    public List<Attr> list() {
+        return logic.list();
+    }
+
+    @Override
+    public Attr read(final String key) {
+        return logic.read(key);
+    }
+
+    @Override
+    public Response create(final Attr configTO) {
+        final Attr config = logic.create(configTO);
+        URI location = 
uriInfo.getAbsolutePathBuilder().path(config.getSchema()).build();
+        return Response.created(location).
+            header(RESTHeaders.RESOURCE_KEY, config.getSchema()).
+            entity(config).
+            build();
+    }
+
+    @Override
+    public void update(final Attr configTO) {
+        logic.update(configTO);
+    }
+
+    @Override
+    public void delete(final String key) {
+        logic.delete(key);
+    }
+
+    @Override
+    public void pushToWA() {
+        logic.pushToWA();
+    }
+}
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/WAConfigDAO.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/WAConfigDAO.java
new file mode 100644
index 0000000..7fff8a6
--- /dev/null
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/WAConfigDAO.java
@@ -0,0 +1,40 @@
+/*
+ * 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.auth;
+
+import org.apache.syncope.core.persistence.api.dao.DAO;
+import org.apache.syncope.core.persistence.api.entity.auth.WAConfigEntry;
+
+import java.util.List;
+
+public interface WAConfigDAO extends DAO<WAConfigEntry> {
+
+    WAConfigEntry find(String key);
+
+    List<WAConfigEntry> findAll();
+
+    WAConfigEntry save(WAConfigEntry configEntry);
+
+    void delete(String key);
+
+    void delete(WAConfigEntry configEntry);
+
+    void deleteAll();
+
+}
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/WAConfigEntry.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/WAConfigEntry.java
new file mode 100644
index 0000000..f4cc6cf
--- /dev/null
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/WAConfigEntry.java
@@ -0,0 +1,30 @@
+/*
+ * 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.auth;
+
+import org.apache.syncope.core.persistence.api.entity.ProvidedKeyEntity;
+
+import java.util.List;
+
+public interface WAConfigEntry extends ProvidedKeyEntity {
+
+    List<String> getValues();
+
+    void setValues(List<String> value);
+}
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAWAConfigDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAWAConfigDAO.java
new file mode 100644
index 0000000..af82fbd
--- /dev/null
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAWAConfigDAO.java
@@ -0,0 +1,74 @@
+/*
+ * 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.auth;
+
+import org.apache.syncope.core.persistence.api.dao.auth.WAConfigDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.WAConfigEntry;
+import org.apache.syncope.core.persistence.jpa.dao.AbstractDAO;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPAWAConfigEntry;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+
+import java.util.List;
+
+@Repository
+public class JPAWAConfigDAO extends AbstractDAO<WAConfigEntry> implements 
WAConfigDAO {
+    @Transactional(readOnly = true)
+    @Override
+    public WAConfigEntry find(final String key) {
+        return entityManager().find(JPAWAConfigEntry.class, key);
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public List<WAConfigEntry> findAll() {
+        TypedQuery<WAConfigEntry> query = entityManager().createQuery(
+            "SELECT e FROM " + JPAWAConfigEntry.class.getSimpleName() + " e", 
WAConfigEntry.class);
+        return query.getResultList();
+    }
+
+    @Override
+    public WAConfigEntry save(final WAConfigEntry configEntry) {
+        return entityManager().merge(configEntry);
+    }
+
+    @Override
+    public void delete(final String key) {
+        WAConfigEntry entry = find(key);
+        if (entry == null) {
+            return;
+        }
+        delete(entry);
+    }
+
+    @Override
+    public void delete(final WAConfigEntry configEntry) {
+        entityManager().remove(configEntry);
+    }
+
+    @Override
+    public void deleteAll() {
+        Query delete = entityManager().createQuery("DELETE FROM " + 
JPAWAConfigEntry.class.getSimpleName());
+        delete.executeUpdate();
+    }
+}
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
index 5ec9130..333ba50 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
@@ -65,6 +65,7 @@ import 
org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
 import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
 import org.apache.syncope.core.persistence.api.entity.auth.SAML2SPKeystore;
 import org.apache.syncope.core.persistence.api.entity.auth.SAML2SPMetadata;
+import org.apache.syncope.core.persistence.api.entity.auth.WAConfigEntry;
 import org.apache.syncope.core.persistence.api.entity.group.GPlainAttr;
 import 
org.apache.syncope.core.persistence.api.entity.group.GPlainAttrUniqueValue;
 import org.apache.syncope.core.persistence.api.entity.group.GPlainAttrValue;
@@ -121,6 +122,7 @@ import 
org.apache.syncope.core.persistence.jpa.entity.auth.JPAOIDCRP;
 import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2SP;
 import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2SPKeystore;
 import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2SPMetadata;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPAWAConfigEntry;
 import org.apache.syncope.core.persistence.jpa.entity.group.JPAGPlainAttr;
 import 
org.apache.syncope.core.persistence.jpa.entity.group.JPAGPlainAttrUniqueValue;
 import org.apache.syncope.core.persistence.jpa.entity.group.JPAGPlainAttrValue;
@@ -345,6 +347,8 @@ public class JPAEntityFactory implements EntityFactory {
             result = (E) new JPAAuthProfile();
         } else if (reference.equals(OIDCJWKS.class)) {
             result = (E) new JPAOIDCJWKS();
+        } else if (reference.equals(WAConfigEntry.class)) {
+            result = (E) new JPAWAConfigEntry();
         } else {
             throw new IllegalArgumentException("Could not find a JPA 
implementation of " + reference.getName());
         }
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAWAConfigEntry.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAWAConfigEntry.java
new file mode 100644
index 0000000..bd75c1c
--- /dev/null
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAWAConfigEntry.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.core.persistence.jpa.entity.auth;
+
+import org.apache.syncope.core.persistence.api.entity.auth.WAConfigEntry;
+import 
org.apache.syncope.core.persistence.jpa.entity.AbstractProvidedKeyEntity;
+
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.Table;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@Table(name = JPAWAConfigEntry.TABLE)
+public class JPAWAConfigEntry extends AbstractProvidedKeyEntity implements 
WAConfigEntry {
+    public static final String TABLE = "ConfigEntry";
+
+    private static final long serialVersionUID = 6422422526695279794L;
+
+    @ElementCollection
+    @CollectionTable(name = TABLE + "Values", joinColumns = @JoinColumn(name = 
"id"))
+    @Column(nullable = false)
+    private List<String> values = new ArrayList<>();
+
+    @Override
+    public List<String> getValues() {
+        return values;
+    }
+
+    public void setValues(final List<String> values) {
+        this.values = values;
+    }
+}
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/WAConfigTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/WAConfigTest.java
new file mode 100644
index 0000000..eab1ae6
--- /dev/null
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/WAConfigTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.inner;
+
+import org.apache.syncope.core.persistence.api.dao.auth.WAConfigDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.WAConfigEntry;
+import org.apache.syncope.core.persistence.jpa.AbstractTest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@Transactional("Master")
+public class WAConfigTest extends AbstractTest {
+
+    @Autowired
+    private WAConfigDAO configDAO;
+
+    @BeforeEach
+    public void beforeEach() {
+        configDAO.deleteAll();
+    }
+
+    @Test
+    public void saveCommaSeparatedValueStrings() {
+        create("system.example.key[0]", Arrays.asList("value1", "value2", 
"value3"));
+        assertFalse(configDAO.findAll().isEmpty());
+    }
+
+    @Test
+    public void saveNumbers() {
+        create("system.example.key[0]", List.of("1984"));
+        assertFalse(configDAO.findAll().isEmpty());
+    }
+
+    @Test
+    public void saveCollection() {
+        WAConfigEntry entry = create("system.example.key[0]", new 
ArrayList<>(Arrays.asList("1", "2")));
+        assertNotNull(entry.getValues());
+        assertFalse(configDAO.findAll().isEmpty());
+    }
+
+    @Test
+    public void saveMap() {
+        HashMap<String, Double> map = new HashMap<>();
+        create("system.example.key[0].key1", List.of("value1"));
+        assertFalse(configDAO.findAll().isEmpty());
+    }
+
+    @Test
+    public void update() {
+        WAConfigEntry entry = create("system.syncope.key[0]", 
Arrays.asList("1", "2", "3", "4"));
+        assertNotNull(entry);
+        entry.setValues(List.of("v1"));
+
+        entry = configDAO.save(entry);
+        assertNotNull(entry);
+        assertNotNull(entry.getKey());
+        WAConfigEntry found = configDAO.find(entry.getKey());
+        assertNotNull(found);
+        assertEquals(List.of("v1"), found.getValues());
+    }
+
+    private WAConfigEntry create(final String name, final List<String> value) {
+        WAConfigEntry entry = entityFactory.newEntity(WAConfigEntry.class);
+        entry.setKey(name);
+        entry.setValues(value);
+        configDAO.save(entry);
+        assertNotNull(entry);
+        assertNotNull(entry.getKey());
+        assertNotNull(configDAO.find(entry.getKey()));
+        return entry;
+    }
+
+}
diff --git 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/WAConfigDataBinder.java
 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/WAConfigDataBinder.java
new file mode 100644
index 0000000..0faa17a
--- /dev/null
+++ 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/WAConfigDataBinder.java
@@ -0,0 +1,31 @@
+/*
+ * 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.Attr;
+import org.apache.syncope.core.persistence.api.entity.auth.WAConfigEntry;
+
+public interface WAConfigDataBinder {
+    Attr getAttr(WAConfigEntry waConfigEntry);
+
+    WAConfigEntry create(Attr config);
+
+    WAConfigEntry update(WAConfigEntry entry, Attr config);
+}
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/WAConfigDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/WAConfigDataBinderImpl.java
new file mode 100644
index 0000000..b67db28
--- /dev/null
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/WAConfigDataBinderImpl.java
@@ -0,0 +1,61 @@
+/*
+ * 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 org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.auth.WAConfigEntry;
+import org.apache.syncope.core.provisioning.api.data.WAConfigDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class WAConfigDataBinderImpl implements WAConfigDataBinder {
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @Override
+    public Attr getAttr(final WAConfigEntry waConfigEntry) {
+        return new 
Attr.Builder(waConfigEntry.getKey()).values(waConfigEntry.getValues()).build();
+    }
+
+    @Override
+    public WAConfigEntry create(final Attr configTO) {
+        return update(entityFactory.newEntity(WAConfigEntry.class), configTO);
+    }
+
+    @Override
+    public WAConfigEntry update(final WAConfigEntry entry, final Attr 
configTO) {
+        return getConfigEntry(entry, configTO);
+    }
+
+    private WAConfigEntry getConfigEntry(
+        final WAConfigEntry configEntry,
+        final Attr config) {
+
+        WAConfigEntry result = configEntry;
+        if (result == null) {
+            result = entityFactory.newEntity(WAConfigEntry.class);
+        }
+        result.setValues(config.getValues());
+        result.setKey(config.getSchema());
+        return result;
+    }
+}
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 6c607c0..a4f69c4 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
@@ -146,6 +146,7 @@ import 
org.apache.syncope.common.rest.api.service.SAML2SPMetadataService;
 import org.apache.syncope.common.rest.api.service.SRARouteService;
 import org.apache.syncope.common.rest.api.service.UserWorkflowTaskService;
 import org.apache.syncope.common.rest.api.service.wa.U2FRegistrationService;
+import org.apache.syncope.common.rest.api.service.wa.WAConfigService;
 import org.apache.syncope.fit.core.CoreITContext;
 import org.apache.syncope.fit.core.UserITCase;
 import org.junit.jupiter.api.BeforeAll;
@@ -340,6 +341,8 @@ public abstract class AbstractITCase {
 
     protected static U2FRegistrationService u2FRegistrationService;
 
+    protected static WAConfigService waConfigService;
+
     @BeforeAll
     public static void securitySetup() {
         try (InputStream propStream = 
AbstractITCase.class.getResourceAsStream("/security.properties")) {
@@ -419,6 +422,7 @@ public abstract class AbstractITCase {
         authProfileService = adminClient.getService(AuthProfileService.class);
         oidcJWKSService = adminClient.getService(OIDCJWKSService.class);
         u2FRegistrationService = 
adminClient.getService(U2FRegistrationService.class);
+        waConfigService = adminClient.getService(WAConfigService.class);
     }
 
     @Autowired
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/WAConfigITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/WAConfigITCase.java
new file mode 100644
index 0000000..0fc7b68
--- /dev/null
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/WAConfigITCase.java
@@ -0,0 +1,74 @@
+/*
+ * 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 org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.fit.AbstractITCase;
+import org.junit.jupiter.api.Test;
+
+import javax.ws.rs.core.Response;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class WAConfigITCase extends AbstractITCase {
+    private static Attr runTest(final List<String> initialValue, final 
List<String> updatedValue) {
+        Attr configTO = new Attr.Builder(UUID.randomUUID().toString())
+            .values(initialValue)
+            .build();
+        Response response = waConfigService.create(configTO);
+        String key = response.getHeaderString(RESTHeaders.RESOURCE_KEY);
+        assertNotNull(key);
+
+        assertFalse(waConfigService.list().isEmpty());
+
+        configTO = waConfigService.read(key);
+        assertNotNull(configTO);
+
+        configTO = new Attr.Builder(configTO.getSchema())
+            .values(updatedValue)
+            .build();
+        waConfigService.update(configTO);
+
+        Attr updatedTO = waConfigService.read(key);
+        updatedTO.getValues().stream().allMatch(((Collection) 
updatedValue)::contains);
+        return updatedTO;
+    }
+
+    private static <T extends Serializable> void deleteEntry(final Attr 
configTO) {
+        waConfigService.delete(configTO.getSchema());
+        assertThrows(SyncopeClientException.class, () -> 
waConfigService.read(configTO.getSchema()));
+    }
+
+    @Test
+    public void verify() {
+        deleteEntry(runTest(List.of("v1", "v2"), List.of("newValue")));
+        deleteEntry(runTest(List.of("12345"), List.of("98765")));
+        deleteEntry(runTest(List.of("123.45"), List.of("987.65")));
+        deleteEntry(runTest(List.of("1", "2", "3"), List.of("4", "5", "6")));
+    }
+}
diff --git 
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
 
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
index a005c5a..566082c 100644
--- 
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
+++ 
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
@@ -18,26 +18,27 @@
  */
 package org.apache.syncope.wa.bootstrap;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
 import org.apereo.cas.configuration.CasConfigurationProperties;
 import org.apereo.cas.configuration.CasCoreConfigurationUtils;
+import 
org.apereo.cas.configuration.model.core.authentication.AuthenticationProperties;
 import 
org.apereo.cas.configuration.model.support.generic.AcceptAuthenticationProperties;
 import 
org.apereo.cas.configuration.model.support.jaas.JaasAuthenticationProperties;
+import 
org.apereo.cas.configuration.model.support.jdbc.JdbcAuthenticationProperties;
 import 
org.apereo.cas.configuration.model.support.jdbc.authn.QueryJdbcAuthenticationProperties;
 import 
org.apereo.cas.configuration.model.support.ldap.LdapAuthenticationProperties;
+import 
org.apereo.cas.configuration.model.support.mfa.MultifactorAuthenticationProperties;
 import 
org.apereo.cas.configuration.model.support.mfa.gauth.GoogleAuthenticatorMultifactorProperties;
 import 
org.apereo.cas.configuration.model.support.mfa.u2f.U2FMultifactorProperties;
+import 
org.apereo.cas.configuration.model.support.pac4j.Pac4jDelegatedAuthenticationProperties;
 import 
org.apereo.cas.configuration.model.support.pac4j.oidc.Pac4jGenericOidcClientProperties;
 import 
org.apereo.cas.configuration.model.support.pac4j.oidc.Pac4jOidcClientProperties;
 import 
org.apereo.cas.configuration.model.support.pac4j.saml.Pac4jSamlClientProperties;
 import org.apereo.cas.configuration.model.support.radius.RadiusProperties;
 import 
org.apereo.cas.configuration.model.support.syncope.SyncopeAuthenticationProperties;
 import org.apereo.cas.util.model.TriStateBoolean;
+
+import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
+import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.auth.AuthModuleConf;
@@ -52,6 +53,7 @@ import 
org.apache.syncope.common.lib.auth.StaticAuthModuleConf;
 import org.apache.syncope.common.lib.auth.SyncopeAuthModuleConf;
 import org.apache.syncope.common.lib.auth.U2FAuthModuleConf;
 import org.apache.syncope.common.rest.api.service.AuthModuleService;
+import org.apache.syncope.common.rest.api.service.wa.WAConfigService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
@@ -60,6 +62,11 @@ import org.springframework.core.env.Environment;
 import org.springframework.core.env.MapPropertySource;
 import org.springframework.core.env.PropertySource;
 
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
 @Order
 public class SyncopeWAPropertySourceLocator implements PropertySourceLocator {
 
@@ -71,41 +78,51 @@ public class SyncopeWAPropertySourceLocator implements 
PropertySourceLocator {
         this.waRestClient = waRestClient;
     }
 
-    private static String mapAuthModule(
-            final CasConfigurationProperties casProperties,
-            final String authModule,
-            final SyncopeAuthModuleConf conf,
-            final String address) {
+    private static Map<String, Object> mapAuthModule(
+
+        final String authModule,
+        final SyncopeAuthModuleConf conf,
+        final String address) {
 
         SyncopeAuthenticationProperties syncopeProps = new 
SyncopeAuthenticationProperties();
         syncopeProps.setName(authModule);
         syncopeProps.setDomain(conf.getDomain());
         syncopeProps.setUrl(StringUtils.substringBefore(address, "/rest"));
 
+        CasConfigurationProperties casProperties = new 
CasConfigurationProperties();
         casProperties.getAuthn().setSyncope(syncopeProps);
-        return "cas.authn.syncope.";
+        SimpleFilterProvider filterProvider = getParentCasFilterProvider();
+        
filterProvider.addFilter(AuthenticationProperties.class.getSimpleName(),
+            SimpleBeanPropertyFilter.filterOutAllExcept(
+                
CasCoreConfigurationUtils.getPropertyName(AuthenticationProperties.class,
+                    AuthenticationProperties::getSyncope)));
+        return filterCasProperties(casProperties, filterProvider);
     }
 
-    private static String mapAuthModule(
-            final CasConfigurationProperties casProperties,
-            final String authModule,
-            final StaticAuthModuleConf conf) {
+    private static Map<String, Object> mapAuthModule(
+        final String authModule,
+        final StaticAuthModuleConf conf) {
 
         AcceptAuthenticationProperties staticProps = new 
AcceptAuthenticationProperties();
         staticProps.setName(authModule);
         String users = conf.getUsers().entrySet().stream().
-                map(entry -> entry.getKey() + "::" + entry.getValue()).
-                collect(Collectors.joining(","));
+            map(entry -> entry.getKey() + "::" + entry.getValue()).
+            collect(Collectors.joining(","));
         staticProps.setUsers(users);
 
+        CasConfigurationProperties casProperties = new 
CasConfigurationProperties();
+        SimpleFilterProvider filterProvider = getParentCasFilterProvider();
         casProperties.getAuthn().setAccept(staticProps);
-        return "cas.authn.accept.";
+        
filterProvider.addFilter(AuthenticationProperties.class.getSimpleName(),
+            SimpleBeanPropertyFilter.filterOutAllExcept(
+                
CasCoreConfigurationUtils.getPropertyName(AuthenticationProperties.class,
+                    AuthenticationProperties::getAccept)));
+        return filterCasProperties(casProperties, filterProvider);
     }
 
-    private static String mapAuthModule(
-            final CasConfigurationProperties casProperties,
-            final String authModule,
-            final LDAPAuthModuleConf conf) {
+    private static Map<String, Object> mapAuthModule(
+        final String authModule,
+        final LDAPAuthModuleConf conf) {
 
         LdapAuthenticationProperties ldapProps = new 
LdapAuthenticationProperties();
         ldapProps.setName(authModule);
@@ -117,14 +134,19 @@ public class SyncopeWAPropertySourceLocator implements 
PropertySourceLocator {
         ldapProps.setSubtreeSearch(conf.isSubtreeSearch());
         ldapProps.setPrincipalAttributeList(conf.getPrincipalAttributeList());
 
+        CasConfigurationProperties casProperties = new 
CasConfigurationProperties();
+        SimpleFilterProvider filterProvider = getParentCasFilterProvider();
         casProperties.getAuthn().getLdap().add(ldapProps);
-        return "cas.authn.ldap.";
+        
filterProvider.addFilter(AuthenticationProperties.class.getSimpleName(),
+            SimpleBeanPropertyFilter.filterOutAllExcept(
+                
CasCoreConfigurationUtils.getPropertyName(AuthenticationProperties.class,
+                    AuthenticationProperties::getLdap)));
+        return filterCasProperties(casProperties, filterProvider);
     }
 
-    private static String mapAuthModule(
-            final CasConfigurationProperties casProperties,
-            final String authModule,
-            final GoogleMfaAuthModuleConf conf) {
+    private static Map<String, Object> mapAuthModule(
+        final String authModule,
+        final GoogleMfaAuthModuleConf conf) {
 
         GoogleAuthenticatorMultifactorProperties props = new 
GoogleAuthenticatorMultifactorProperties();
         props.setName(authModule);
@@ -134,14 +156,24 @@ public class SyncopeWAPropertySourceLocator implements 
PropertySourceLocator {
         props.setTimeStepSize(conf.getTimeStepSize());
         props.setWindowSize(conf.getWindowSize());
 
+        CasConfigurationProperties casProperties = new 
CasConfigurationProperties();
+        SimpleFilterProvider filterProvider = getParentCasFilterProvider();
         casProperties.getAuthn().getMfa().setGauth(props);
-        return "cas.authn.mfa.gauth.";
+        filterProvider.
+            addFilter(AuthenticationProperties.class.getSimpleName(),
+                SimpleBeanPropertyFilter.filterOutAllExcept(
+                    
CasCoreConfigurationUtils.getPropertyName(AuthenticationProperties.class,
+                        AuthenticationProperties::getMfa))).
+            
addFilter(MultifactorAuthenticationProperties.class.getSimpleName(),
+                SimpleBeanPropertyFilter.filterOutAllExcept(
+                    
CasCoreConfigurationUtils.getPropertyName(MultifactorAuthenticationProperties.class,
+                        MultifactorAuthenticationProperties::getGauth)));
+        return filterCasProperties(casProperties, filterProvider);
     }
 
-    private static String mapAuthModule(
-            final CasConfigurationProperties casProperties,
-            final String authModule,
-            final U2FAuthModuleConf conf) {
+    private static Map<String, Object> mapAuthModule(
+        final String authModule,
+        final U2FAuthModuleConf conf) {
 
         U2FMultifactorProperties props = new U2FMultifactorProperties();
         props.setName(authModule);
@@ -150,14 +182,25 @@ public class SyncopeWAPropertySourceLocator implements 
PropertySourceLocator {
         props.setExpireRegistrations(conf.getExpireRegistrations());
         
props.setExpireRegistrationsTimeUnit(TimeUnit.valueOf(conf.getExpireRegistrationsTimeUnit()));
 
+        CasConfigurationProperties casProperties = new 
CasConfigurationProperties();
+        SimpleFilterProvider filterProvider = getParentCasFilterProvider();
         casProperties.getAuthn().getMfa().setU2f(props);
-        return "cas.authn.mfa.u2f.";
+
+        filterProvider.
+            addFilter(AuthenticationProperties.class.getSimpleName(),
+                SimpleBeanPropertyFilter.filterOutAllExcept(
+                    
CasCoreConfigurationUtils.getPropertyName(AuthenticationProperties.class,
+                        AuthenticationProperties::getMfa))).
+            
addFilter(MultifactorAuthenticationProperties.class.getSimpleName(),
+                SimpleBeanPropertyFilter.filterOutAllExcept(
+                    
CasCoreConfigurationUtils.getPropertyName(MultifactorAuthenticationProperties.class,
+                        MultifactorAuthenticationProperties::getU2f)));
+        return filterCasProperties(casProperties, filterProvider);
     }
 
-    private static String mapAuthModule(
-            final CasConfigurationProperties casProperties,
-            final String authModule,
-            final JaasAuthModuleConf conf) {
+    private static Map<String, Object> mapAuthModule(
+        final String authModule,
+        final JaasAuthModuleConf conf) {
 
         JaasAuthenticationProperties props = new 
JaasAuthenticationProperties();
         props.setName(authModule);
@@ -167,14 +210,20 @@ public class SyncopeWAPropertySourceLocator implements 
PropertySourceLocator {
         props.setLoginConfigType(conf.getLoginConfigurationFile());
         props.setRealm(conf.getRealm());
 
+        CasConfigurationProperties casProperties = new 
CasConfigurationProperties();
+        SimpleFilterProvider filterProvider = getParentCasFilterProvider();
         casProperties.getAuthn().getJaas().add(props);
-        return "cas.authn.jaas.";
+
+        
filterProvider.addFilter(AuthenticationProperties.class.getSimpleName(),
+            SimpleBeanPropertyFilter.filterOutAllExcept(
+                
CasCoreConfigurationUtils.getPropertyName(AuthenticationProperties.class,
+                    AuthenticationProperties::getJaas)));
+        return filterCasProperties(casProperties, filterProvider);
     }
 
-    private static String mapAuthModule(
-            final CasConfigurationProperties casProperties,
-            final String authModule,
-            final JDBCAuthModuleConf conf) {
+    private static Map<String, Object> mapAuthModule(
+        final String authModule,
+        final JDBCAuthModuleConf conf) {
 
         QueryJdbcAuthenticationProperties props = new 
QueryJdbcAuthenticationProperties();
         props.setName(authModule);
@@ -189,14 +238,25 @@ public class SyncopeWAPropertySourceLocator implements 
PropertySourceLocator {
         props.setUser(conf.getUser());
         props.setPrincipalAttributeList(conf.getPrincipalAttributeList());
 
+        CasConfigurationProperties casProperties = new 
CasConfigurationProperties();
+        SimpleFilterProvider filterProvider = getParentCasFilterProvider();
         casProperties.getAuthn().getJdbc().getQuery().add(props);
-        return "cas.authn.jdbc.query.";
+
+        filterProvider.
+            addFilter(AuthenticationProperties.class.getSimpleName(),
+                SimpleBeanPropertyFilter.filterOutAllExcept(
+                    
CasCoreConfigurationUtils.getPropertyName(AuthenticationProperties.class,
+                        AuthenticationProperties::getJdbc))).
+            
addFilter(MultifactorAuthenticationProperties.class.getSimpleName(),
+                SimpleBeanPropertyFilter.filterOutAllExcept(
+                    
CasCoreConfigurationUtils.getPropertyName(JdbcAuthenticationProperties.class,
+                        JdbcAuthenticationProperties::getQuery)));
+        return filterCasProperties(casProperties, filterProvider);
     }
 
-    private static String mapAuthModule(
-            final CasConfigurationProperties casProperties,
-            final String authModule,
-            final OIDCAuthModuleConf conf) {
+    private static Map<String, Object> mapAuthModule(
+        final String authModule,
+        final OIDCAuthModuleConf conf) {
 
         Pac4jGenericOidcClientProperties props = new 
Pac4jGenericOidcClientProperties();
         props.setId(conf.getId());
@@ -213,14 +273,25 @@ public class SyncopeWAPropertySourceLocator implements 
PropertySourceLocator {
         Pac4jOidcClientProperties client = new Pac4jOidcClientProperties();
         client.setGeneric(props);
 
+        CasConfigurationProperties casProperties = new 
CasConfigurationProperties();
+        SimpleFilterProvider filterProvider = getParentCasFilterProvider();
         casProperties.getAuthn().getPac4j().getOidc().add(client);
-        return "cas.authn.pac4j.oidc.";
+
+        filterProvider.
+            addFilter(AuthenticationProperties.class.getSimpleName(),
+                SimpleBeanPropertyFilter.filterOutAllExcept(
+                    
CasCoreConfigurationUtils.getPropertyName(AuthenticationProperties.class,
+                        AuthenticationProperties::getPac4j))).
+            
addFilter(Pac4jDelegatedAuthenticationProperties.class.getSimpleName(),
+                SimpleBeanPropertyFilter.filterOutAllExcept(
+                    
CasCoreConfigurationUtils.getPropertyName(Pac4jDelegatedAuthenticationProperties.class,
+                        Pac4jDelegatedAuthenticationProperties::getOidc)));
+        return filterCasProperties(casProperties, filterProvider);
     }
 
-    private static String mapAuthModule(
-            final CasConfigurationProperties casProperties,
-            final String authModule,
-            final RadiusAuthModuleConf conf) {
+    private static Map<String, Object> mapAuthModule(
+        final String authModule,
+        final RadiusAuthModuleConf conf) {
 
         RadiusProperties props = new RadiusProperties();
         props.setName(authModule);
@@ -241,14 +312,20 @@ public class SyncopeWAPropertySourceLocator implements 
PropertySourceLocator {
         props.getServer().setProtocol(conf.getProtocol());
         props.getServer().setRetries(conf.getRetries());
 
+        CasConfigurationProperties casProperties = new 
CasConfigurationProperties();
+        SimpleFilterProvider filterProvider = getParentCasFilterProvider();
         casProperties.getAuthn().setRadius(props);
-        return "cas.authn.radius.";
+        filterProvider.
+            addFilter(AuthenticationProperties.class.getSimpleName(),
+                SimpleBeanPropertyFilter.filterOutAllExcept(
+                    
CasCoreConfigurationUtils.getPropertyName(AuthenticationProperties.class,
+                        AuthenticationProperties::getRadius)));
+        return filterCasProperties(casProperties, filterProvider);
     }
 
-    private static String mapAuthModule(
-            final CasConfigurationProperties casProperties,
-            final String authModule,
-            final SAML2IdPAuthModuleConf conf) {
+    private static Map<String, Object> mapAuthModule(
+        final String authModule,
+        final SAML2IdPAuthModuleConf conf) {
 
         Pac4jSamlClientProperties props = new Pac4jSamlClientProperties();
         props.setClientName(authModule);
@@ -272,11 +349,36 @@ public class SyncopeWAPropertySourceLocator implements 
PropertySourceLocator {
         
props.setSignatureReferenceDigestMethods(conf.getSignatureReferenceDigestMethods());
         props.setPrincipalAttributeId(conf.getUserIdAttribute());
         
props.setNameIdPolicyAllowCreate(StringUtils.isBlank(conf.getNameIdPolicyAllowCreate())
-                ? TriStateBoolean.UNDEFINED
-                : 
TriStateBoolean.valueOf(conf.getNameIdPolicyAllowCreate().toUpperCase()));
+            ? TriStateBoolean.UNDEFINED
+            : 
TriStateBoolean.valueOf(conf.getNameIdPolicyAllowCreate().toUpperCase()));
 
+        CasConfigurationProperties casProperties = new 
CasConfigurationProperties();
+        SimpleFilterProvider filterProvider = getParentCasFilterProvider();
         casProperties.getAuthn().getPac4j().getSaml().add(props);
-        return "cas.authn.pac4j.saml.";
+
+        filterProvider.
+            addFilter(AuthenticationProperties.class.getSimpleName(),
+                SimpleBeanPropertyFilter.filterOutAllExcept(
+                    
CasCoreConfigurationUtils.getPropertyName(AuthenticationProperties.class,
+                        AuthenticationProperties::getPac4j))).
+            
addFilter(Pac4jDelegatedAuthenticationProperties.class.getSimpleName(),
+                SimpleBeanPropertyFilter.filterOutAllExcept(
+                    
CasCoreConfigurationUtils.getPropertyName(Pac4jDelegatedAuthenticationProperties.class,
+                        Pac4jDelegatedAuthenticationProperties::getSaml)));
+        return filterCasProperties(casProperties, filterProvider);
+    }
+
+    private static SimpleFilterProvider getParentCasFilterProvider() {
+        return new SimpleFilterProvider().
+            setFailOnUnknownId(false).
+            addFilter(CasConfigurationProperties.class.getSimpleName(), 
SimpleBeanPropertyFilter.filterOutAllExcept(
+                
CasCoreConfigurationUtils.getPropertyName(CasConfigurationProperties.class,
+                    CasConfigurationProperties::getAuthn)));
+    }
+
+    private static Map<String, Object> filterCasProperties(final 
CasConfigurationProperties casProperties,
+                                                           final 
SimpleFilterProvider filters) {
+        return CasCoreConfigurationUtils.asMap(casProperties.withHolder(), 
filters);
     }
 
     @Override
@@ -288,48 +390,39 @@ public class SyncopeWAPropertySourceLocator implements 
PropertySourceLocator {
         }
 
         LOG.info("Bootstrapping WA configuration");
-
-        CasConfigurationProperties casProperties = new 
CasConfigurationProperties();
-        List<String> filters = new ArrayList<>();
+        Map<String, Object> properties = new TreeMap<>();
 
         
syncopeClient.getService(AuthModuleService.class).list().forEach(authModuleTO 
-> {
             AuthModuleConf authConf = authModuleTO.getConf();
             LOG.debug("Mapping auth module {} ", authModuleTO.getKey());
 
             if (authConf instanceof LDAPAuthModuleConf) {
-                filters.add(mapAuthModule(casProperties, 
authModuleTO.getKey(), (LDAPAuthModuleConf) authConf));
+                properties.putAll(mapAuthModule(authModuleTO.getKey(), 
(LDAPAuthModuleConf) authConf));
             } else if (authConf instanceof StaticAuthModuleConf) {
-                filters.add(mapAuthModule(casProperties, 
authModuleTO.getKey(), (StaticAuthModuleConf) authConf));
+                properties.putAll(mapAuthModule(authModuleTO.getKey(), 
(StaticAuthModuleConf) authConf));
             } else if (authConf instanceof SyncopeAuthModuleConf) {
-                filters.add(mapAuthModule(
-                        casProperties,
-                        authModuleTO.getKey(),
-                        (SyncopeAuthModuleConf) authConf,
-                        waRestClient.getSyncopeClient().getAddress()));
+                properties.putAll(mapAuthModule(authModuleTO.getKey(),
+                    (SyncopeAuthModuleConf) authConf, 
syncopeClient.getAddress()));
             } else if (authConf instanceof GoogleMfaAuthModuleConf) {
-                filters.add(mapAuthModule(casProperties, 
authModuleTO.getKey(), (GoogleMfaAuthModuleConf) authConf));
+                properties.putAll(mapAuthModule(authModuleTO.getKey(), 
(GoogleMfaAuthModuleConf) authConf));
             } else if (authConf instanceof JaasAuthModuleConf) {
-                filters.add(mapAuthModule(casProperties, 
authModuleTO.getKey(), (JaasAuthModuleConf) authConf));
+                properties.putAll(mapAuthModule(authModuleTO.getKey(), 
(JaasAuthModuleConf) authConf));
             } else if (authConf instanceof JDBCAuthModuleConf) {
-                filters.add(mapAuthModule(casProperties, 
authModuleTO.getKey(), (JDBCAuthModuleConf) authConf));
+                properties.putAll(mapAuthModule(authModuleTO.getKey(), 
(JDBCAuthModuleConf) authConf));
             } else if (authConf instanceof OIDCAuthModuleConf) {
-                filters.add(mapAuthModule(casProperties, 
authModuleTO.getKey(), (OIDCAuthModuleConf) authConf));
+                properties.putAll(mapAuthModule(authModuleTO.getKey(), 
(OIDCAuthModuleConf) authConf));
             } else if (authConf instanceof RadiusAuthModuleConf) {
-                filters.add(mapAuthModule(casProperties, 
authModuleTO.getKey(), (RadiusAuthModuleConf) authConf));
+                properties.putAll(mapAuthModule(authModuleTO.getKey(), 
(RadiusAuthModuleConf) authConf));
             } else if (authConf instanceof SAML2IdPAuthModuleConf) {
-                filters.add(mapAuthModule(casProperties, 
authModuleTO.getKey(), (SAML2IdPAuthModuleConf) authConf));
+                properties.putAll(mapAuthModule(authModuleTO.getKey(), 
(SAML2IdPAuthModuleConf) authConf));
             } else if (authConf instanceof U2FAuthModuleConf) {
-                filters.add(mapAuthModule(casProperties, 
authModuleTO.getKey(), (U2FAuthModuleConf) authConf));
+                properties.putAll(mapAuthModule(authModuleTO.getKey(), 
(U2FAuthModuleConf) authConf));
             }
         });
 
-        Map<String, Object> properties = 
CasCoreConfigurationUtils.asMap(casProperties.withHolder()).
-                entrySet().stream().
-                filter(entry -> filters.stream().filter(Objects::nonNull).
-                anyMatch(prefix -> entry.getKey().startsWith(prefix))).
-                collect(Collectors.toMap(Map.Entry::getKey, 
Map.Entry::getValue));
+        syncopeClient.getService(WAConfigService.class).list()
+            .forEach(configTO -> properties.put(configTO.getSchema(), 
configTO.getValues()));
         LOG.debug("Collected WA properties: {}", properties);
-
         return new MapPropertySource(getClass().getName(), properties);
     }
 }
diff --git 
a/wa/starter/src/test/java/org/apache/syncope/wa/starter/SyncopeCoreTestingServer.java
 
b/wa/starter/src/test/java/org/apache/syncope/wa/starter/SyncopeCoreTestingServer.java
index 05af05d..bcb7adc 100644
--- 
a/wa/starter/src/test/java/org/apache/syncope/wa/starter/SyncopeCoreTestingServer.java
+++ 
b/wa/starter/src/test/java/org/apache/syncope/wa/starter/SyncopeCoreTestingServer.java
@@ -19,17 +19,6 @@
 package org.apache.syncope.wa.starter;
 
 import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Objects;
-import java.util.UUID;
-import java.util.stream.Collectors;
-
-import javax.validation.constraints.NotNull;
-import javax.ws.rs.NotFoundException;
-import javax.ws.rs.core.Response;
-
 import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
 import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
 import org.apache.syncope.common.keymaster.client.api.ServiceOps;
@@ -45,13 +34,24 @@ import org.springframework.context.ApplicationListener;
 import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.stereotype.Component;
 
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
 @Component
 public class SyncopeCoreTestingServer implements 
ApplicationListener<ContextRefreshedEvent> {
 
-    private static final String ADDRESS = "http://localhost:9080/syncope/rest";;
-
     public static final List<WAClientApp> APPS = new ArrayList<>();
 
+    private static final String ADDRESS = "http://localhost:9080/syncope/rest";;
+
     @Autowired
     private ServiceOps serviceOps;
 
@@ -64,11 +64,11 @@ public class SyncopeCoreTestingServer implements 
ApplicationListener<ContextRefr
                 sf.setAddress(ADDRESS);
                 sf.setResourceClasses(WAClientAppService.class, 
GoogleMfaAuthTokenService.class);
                 sf.setResourceProvider(
-                        WAClientAppService.class,
-                        new SingletonResourceProvider(new 
StubWAClientAppService(), true));
+                    WAClientAppService.class,
+                    new SingletonResourceProvider(new 
StubWAClientAppService(), true));
                 sf.setResourceProvider(
-                        GoogleMfaAuthTokenService.class,
-                        new SingletonResourceProvider(new 
StubGoogleMfaAuthTokenService(), true));
+                    GoogleMfaAuthTokenService.class,
+                    new SingletonResourceProvider(new 
StubGoogleMfaAuthTokenService(), true));
                 sf.setProviders(List.of(new JacksonJsonProvider()));
                 sf.create();
 
@@ -125,16 +125,16 @@ public class SyncopeCoreTestingServer implements 
ApplicationListener<ContextRefr
         @Override
         public GoogleMfaAuthToken findTokenFor(@NotNull final String owner, 
@NotNull final Integer token) {
             return tokens.stream()
-                    .filter(to -> to.getToken().equals(token) && 
to.getOwner().equalsIgnoreCase(owner))
-                    .findFirst().get();
+                .filter(to -> to.getToken().equals(token) && 
to.getOwner().equalsIgnoreCase(owner))
+                .findFirst().get();
         }
 
         @Override
         public PagedResult<GoogleMfaAuthToken> findTokensFor(@NotNull final 
String user) {
             PagedResult<GoogleMfaAuthToken> result = new PagedResult<>();
             result.getResult().addAll(tokens.stream().
-                    filter(to -> to.getOwner().equalsIgnoreCase(user)).
-                    collect(Collectors.toList()));
+                filter(to -> to.getOwner().equalsIgnoreCase(user)).
+                collect(Collectors.toList()));
             result.setSize(result.getResult().size());
             result.setTotalCount(result.getSize());
             return result;
@@ -143,8 +143,8 @@ public class SyncopeCoreTestingServer implements 
ApplicationListener<ContextRefr
         @Override
         public GoogleMfaAuthToken findTokenFor(@NotNull final String key) {
             return tokens.stream()
-                    .filter(to -> to.getKey().equalsIgnoreCase(key))
-                    .findFirst().get();
+                .filter(to -> to.getKey().equalsIgnoreCase(key))
+                .findFirst().get();
         }
 
         @Override
@@ -166,13 +166,13 @@ public class SyncopeCoreTestingServer implements 
ApplicationListener<ContextRefr
         @Override
         public WAClientApp read(final Long clientAppId, final ClientAppType 
type) {
             return APPS.stream().filter(app -> Objects.equals(clientAppId, 
app.getClientAppTO().getClientAppId())).
-                    findFirst().orElseThrow(() -> new 
NotFoundException("ClientApp with clientId " + clientAppId));
+                findFirst().orElseThrow(() -> new NotFoundException("ClientApp 
with clientId " + clientAppId));
         }
 
         @Override
         public WAClientApp read(final String name, final ClientAppType type) {
             return APPS.stream().filter(app -> Objects.equals(name, 
app.getClientAppTO().getName())).
-                    findFirst().orElseThrow(() -> new 
NotFoundException("ClientApp with name " + name));
+                findFirst().orElseThrow(() -> new NotFoundException("ClientApp 
with name " + name));
         }
     }
 }

Reply via email to