syncope-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ilgro...@apache.org
Subject [2/2] syncope git commit: [SYNCOPE-1821] Implementation on Core completed; still missing console + doc
Date Thu, 15 Mar 2018 13:10:37 GMT
[SYNCOPE-1821] Implementation on Core completed; still missing console + doc


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/425f9b9e
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/425f9b9e
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/425f9b9e

Branch: refs/heads/master
Commit: 425f9b9ed1a26fa016d8799ccb611c24cf25a04a
Parents: 5724a50
Author: Francesco Chicchiriccò <ilgrosso@apache.org>
Authored: Thu Mar 15 14:10:26 2018 +0100
Committer: Francesco Chicchiriccò <ilgrosso@apache.org>
Committed: Thu Mar 15 14:10:26 2018 +0100

----------------------------------------------------------------------
 .../syncope/client/lib/SyncopeClient.java       |   7 +-
 .../syncope/common/lib/search/SpecialAttr.java  |   4 +
 .../search/UserFiqlSearchConditionBuilder.java  |  24 ++++
 .../syncope/common/lib/search/UserProperty.java |   4 +
 .../syncope/common/lib/to/ApplicationTO.java    |  69 ++++++++++
 .../syncope/common/lib/to/PrivilegeTO.java      |  83 +++++++++++
 .../apache/syncope/common/lib/to/RoleTO.java    |   9 ++
 .../apache/syncope/common/lib/to/UserTO.java    |  11 ++
 .../common/lib/types/ClientExceptionType.java   |   1 +
 .../common/lib/types/StandardEntitlement.java   |  10 ++
 .../rest/api/service/ApplicationService.java    | 132 ++++++++++++++++++
 .../rest/api/service/UserSelfService.java       |  47 ++++---
 .../common/rest/api/service/UserService.java    |  24 ++--
 .../syncope/core/logic/ApplicationLogic.java    | 138 +++++++++++++++++++
 .../apache/syncope/core/logic/UserLogic.java    |   3 +-
 .../persistence/api/dao/ApplicationDAO.java     |  39 ++++++
 .../core/persistence/api/dao/RoleDAO.java       |   3 +
 .../api/dao/search/PrivilegeCond.java           |  39 ++++++
 .../persistence/api/dao/search/SearchCond.java  |  25 +++-
 .../persistence/api/entity/Application.java     |  35 +++++
 .../core/persistence/api/entity/Privilege.java  |  38 +++++
 .../core/persistence/api/entity/Role.java       |   4 +
 .../api/search/SearchCondVisitor.java           |   7 +
 .../api/search/SearchCondConverterTest.java     |  13 ++
 .../jpa/content/XMLContentLoader.java           |   4 +-
 .../persistence/jpa/dao/JPAAnySearchDAO.java    |  43 +++++-
 .../persistence/jpa/dao/JPAApplicationDAO.java  |  84 +++++++++++
 .../core/persistence/jpa/dao/JPARoleDAO.java    |  46 ++++---
 .../core/persistence/jpa/dao/SearchSupport.java |   8 ++
 .../persistence/jpa/entity/JPAApplication.java  |  71 ++++++++++
 .../jpa/entity/JPAEntityFactory.java            |   6 +
 .../persistence/jpa/entity/JPAPrivilege.java    |  90 ++++++++++++
 .../core/persistence/jpa/entity/JPARole.java    |  20 +++
 .../src/main/resources/views.xml                |  14 ++
 .../persistence/jpa/inner/AnySearchTest.java    |  15 ++
 .../persistence/jpa/inner/ApplicationTest.java  | 119 ++++++++++++++++
 .../test/resources/domains/MasterContent.xml    |  11 ++
 .../api/data/ApplicationDataBinder.java         |  35 +++++
 .../java/data/ApplicationDataBinderImpl.java    | 124 +++++++++++++++++
 .../java/data/RoleDataBinderImpl.java           |  23 +++-
 .../java/data/UserDataBinderImpl.java           |  13 +-
 .../cxf/service/ApplicationServiceImpl.java     |  72 ++++++++++
 .../rest/cxf/service/UserSelfServiceImpl.java   |   8 +-
 .../client/ElasticsearchUtils.java              |   5 +
 .../jpa/dao/ElasticsearchAnySearchDAO.java      |   7 +
 fit/core-reference/pom.xml                      |  24 ++++
 .../org/apache/syncope/fit/AbstractITCase.java  |   4 +
 .../syncope/fit/core/ApplicationITCase.java     | 132 ++++++++++++++++++
 .../syncope/fit/core/AuthenticationITCase.java  |  24 ++--
 .../org/apache/syncope/fit/core/RoleITCase.java |  15 +-
 .../apache/syncope/fit/core/SearchITCase.java   |  13 ++
 .../org/apache/syncope/fit/core/UserITCase.java |   7 +
 .../apache/syncope/fit/core/UserSelfITCase.java |  14 +-
 .../reference-guide/concepts/concepts.adoc      |   2 +
 .../reference-guide/concepts/entitlements.adoc  |   4 +-
 .../reference-guide/concepts/privileges.adoc    |  21 +++
 .../reference-guide/concepts/roles.adoc         |   2 +
 .../restfulservices.adoc                        |   7 +-
 58 files changed, 1758 insertions(+), 98 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java b/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
index 2a66f14..7675f0a 100644
--- a/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
@@ -57,6 +57,8 @@ public class SyncopeClient {
 
     private static final String HEADER_SPLIT_PROPERTY = "org.apache.cxf.http.header.split";
 
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
     private final MediaType mediaType;
 
     private final JAXRSClientFactoryBean restClientFactory;
@@ -244,7 +246,6 @@ public class SyncopeClient {
         }
     }
 
-    @SuppressWarnings("unchecked")
     public Pair<Map<String, Set<String>>, UserTO> self() {
         // Explicitly disable header value split because it interferes with JSON deserialization below
         UserSelfService service = getService(UserSelfService.class);
@@ -260,9 +261,9 @@ public class SyncopeClient {
 
         try {
             return Pair.of(
-                    (Map<String, Set<String>>) new ObjectMapper().readValue(
+                    OBJECT_MAPPER.readValue(
                             response.getHeaderString(RESTHeaders.OWNED_ENTITLEMENTS),
-                            new TypeReference<HashMap<String, Set<String>>>() {
+                            new TypeReference<Map<String, Set<String>>>() {
                     }),
                     response.readEntity(UserTO.class));
         } catch (IOException e) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/common/lib/src/main/java/org/apache/syncope/common/lib/search/SpecialAttr.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/search/SpecialAttr.java b/common/lib/src/main/java/org/apache/syncope/common/lib/search/SpecialAttr.java
index 1a90376..80858e8 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/search/SpecialAttr.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/search/SpecialAttr.java
@@ -55,6 +55,10 @@ public enum SpecialAttr {
      */
     ROLES("$roles"),
     /**
+     * Applies to users.
+     */
+    PRIVILEGES("$privileges"),
+    /**
      * Applies to users, groups and any objects.
      */
     DYNREALMS("$dynRealms"),

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserFiqlSearchConditionBuilder.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserFiqlSearchConditionBuilder.java b/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserFiqlSearchConditionBuilder.java
index 8cd7421..88cdc6b 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserFiqlSearchConditionBuilder.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserFiqlSearchConditionBuilder.java
@@ -88,6 +88,18 @@ public class UserFiqlSearchConditionBuilder extends AbstractFiqlSearchConditionB
                 notInRoles(role, moreRoles);
     }
 
+    public CompleteCondition withPrivileges(final String privilege, final String... morePrivileges) {
+        return newBuilderInstance().
+                is(SpecialAttr.PRIVILEGES.toString()).
+                withPrivileges(privilege, morePrivileges);
+    }
+
+    public CompleteCondition withoutPrivileges(final String privilege, final String... morePrivileges) {
+        return newBuilderInstance().
+                is(SpecialAttr.PRIVILEGES.toString()).
+                withoutPrivileges(privilege, morePrivileges);
+    }
+
     protected static class Builder extends AbstractFiqlSearchConditionBuilder.Builder
             implements UserProperty, CompleteCondition {
 
@@ -153,5 +165,17 @@ public class UserFiqlSearchConditionBuilder extends AbstractFiqlSearchConditionB
             this.result = SpecialAttr.ROLES.toString();
             return condition(FiqlParser.NEQ, role, (Object[]) moreRoles);
         }
+
+        @Override
+        public CompleteCondition withPrivileges(final String privilege, final String... morePrivileges) {
+            this.result = SpecialAttr.PRIVILEGES.toString();
+            return condition(FiqlParser.EQ, privilege, (Object[]) morePrivileges);
+        }
+
+        @Override
+        public CompleteCondition withoutPrivileges(final String privilege, final String... morePrivileges) {
+            this.result = SpecialAttr.PRIVILEGES.toString();
+            return condition(FiqlParser.NEQ, privilege, (Object[]) morePrivileges);
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserProperty.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserProperty.java b/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserProperty.java
index 4c717f9..7ac5be6 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserProperty.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserProperty.java
@@ -38,4 +38,8 @@ public interface UserProperty extends SyncopeProperty {
 
     CompleteCondition notInRoles(String role, String... moreRoles);
 
+    CompleteCondition withPrivileges(String privilege, String... morePrivileges);
+
+    CompleteCondition withoutPrivileges(String privilege, String... morePrivileges);
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/common/lib/src/main/java/org/apache/syncope/common/lib/to/ApplicationTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ApplicationTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ApplicationTO.java
new file mode 100644
index 0000000..798c0be
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ApplicationTO.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.PathParam;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "application")
+@XmlType
+public class ApplicationTO extends AbstractBaseBean implements EntityTO {
+
+    private static final long serialVersionUID = -4117796727736925215L;
+
+    private String key;
+
+    private String description;
+
+    private final List<PrivilegeTO> privileges = new ArrayList<>();
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @PathParam("key")
+    @Override
+    public void setKey(final String key) {
+        this.key = key;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    @XmlElementWrapper(name = "privileges")
+    @XmlElement(name = "privilege")
+    @JsonProperty("privileges")
+    public List<PrivilegeTO> getPrivileges() {
+        return privileges;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/common/lib/src/main/java/org/apache/syncope/common/lib/to/PrivilegeTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/PrivilegeTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/PrivilegeTO.java
new file mode 100644
index 0000000..5c9ed89
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/PrivilegeTO.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "privilege")
+@XmlType
+public class PrivilegeTO extends AbstractBaseBean implements EntityTO {
+
+    private static final long serialVersionUID = 5461846770586031758L;
+
+    private String key;
+
+    private String description;
+
+    private String application;
+
+    private String specMimeType;
+
+    private String spec;
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @Override
+    public void setKey(final String key) {
+        this.key = key;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    public String getApplication() {
+        return application;
+    }
+
+    public void setApplication(final String application) {
+        this.application = application;
+    }
+
+    public String getSpecMimeType() {
+        return specMimeType;
+    }
+
+    public void setSpecMimeType(final String specMimeType) {
+        this.specMimeType = specMimeType;
+    }
+
+    public String getSpec() {
+        return spec;
+    }
+
+    public void setSpec(final String specification) {
+        this.spec = specification;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/common/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java
index 9313349..a474b6b 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java
@@ -46,6 +46,8 @@ public class RoleTO extends AbstractBaseBean implements EntityTO {
 
     private String dynMembershipCond;
 
+    private final Set<String> privileges = new HashSet<>();
+
     @Override
     public String getKey() {
         return key;
@@ -86,4 +88,11 @@ public class RoleTO extends AbstractBaseBean implements EntityTO {
         this.dynMembershipCond = dynMembershipCond;
     }
 
+    @XmlElementWrapper(name = "privileges")
+    @XmlElement(name = "privilege")
+    @JsonProperty("privileges")
+    public Set<String> getPrivileges() {
+        return privileges;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/common/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java
index 0341f48..66873ee 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java
@@ -23,8 +23,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlRootElement;
@@ -46,6 +48,8 @@ public class UserTO extends AnyTO implements GroupableRelatableTO {
 
     private final List<String> dynRoles = new ArrayList<>();
 
+    private final Set<String> privileges = new HashSet<>();
+
     private String token;
 
     private Date tokenExpireTime;
@@ -112,6 +116,13 @@ public class UserTO extends AnyTO implements GroupableRelatableTO {
         return dynRoles;
     }
 
+    @XmlElementWrapper(name = "privileges")
+    @XmlElement(name = "privilege")
+    @JsonProperty("privileges")
+    public Set<String> getPrivileges() {
+        return privileges;
+    }
+
     @Schema(readOnly = true)
     public String getToken() {
         return token;

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
index 7807272..ada4a1e 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
@@ -31,6 +31,7 @@ public enum ClientExceptionType {
     EntityExists(Response.Status.CONFLICT),
     GenericPersistence(Response.Status.BAD_REQUEST),
     HasChildren(Response.Status.BAD_REQUEST),
+    InvalidPrivilege(Response.Status.BAD_REQUEST),
     InvalidImplementation(Response.Status.BAD_REQUEST),
     InvalidSecurityAnswer(Response.Status.BAD_REQUEST),
     InvalidEntity(Response.Status.BAD_REQUEST),

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
index 12066f8..00a6638 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
@@ -86,6 +86,16 @@ public final class StandardEntitlement {
 
     public static final String ROLE_DELETE = "ROLE_DELETE";
 
+    public static final String APPLICATION_LIST = "APPLICATION_LIST";
+
+    public static final String APPLICATION_CREATE = "APPLICATION_CREATE";
+
+    public static final String APPLICATION_READ = "APPLICATION_READ";
+
+    public static final String APPLICATION_UPDATE = "APPLICATION_UPDATE";
+
+    public static final String APPLICATION_DELETE = "APPLICATION_DELETE";
+
     public static final String DYNREALM_CREATE = "DYNREALM_CREATE";
 
     public static final String DYNREALM_READ = "DYNREALM_READ";

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ApplicationService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ApplicationService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ApplicationService.java
new file mode 100644
index 0000000..5f3c893
--- /dev/null
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ApplicationService.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.rest.api.service;
+
+import io.swagger.v3.oas.annotations.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 java.util.List;
+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 org.apache.syncope.common.lib.to.ApplicationTO;
+import org.apache.syncope.common.lib.to.PrivilegeTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+
+/**
+ * REST operations for applications.
+ */
+@Tag(name = "Applications")
+@SecurityRequirements({
+    @SecurityRequirement(name = "BasicAuthentication")
+    ,
+    @SecurityRequirement(name = "Bearer") })
+@Path("applications")
+public interface ApplicationService extends JAXRSService {
+
+    /**
+     * Returns a list of all applications.
+     *
+     * @return list of all applications.
+     */
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    List<ApplicationTO> list();
+
+    /**
+     * Returns application with matching key.
+     *
+     * @param key application key to be read
+     * @return application with matching key
+     */
+    @GET
+    @Path("{key}")
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    ApplicationTO read(@NotNull @PathParam("key") String key);
+
+    /**
+     * Returns privilege with matching key.
+     *
+     * @param key privilege key to be read
+     * @return privilege with matching key
+     */
+    @GET
+    @Path("privileges/{key}")
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    PrivilegeTO readPrivilege(@NotNull @PathParam("key") String key);
+
+    /**
+     * Creates a new application.
+     *
+     * @param applicationTO application to be created
+     * @return Response object featuring Location header of created application
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "201",
+                    description = "Application successfully created", headers = {
+                @Header(name = RESTHeaders.RESOURCE_KEY, schema =
+                        @Schema(type = "string"),
+                        description = "Key value for the entity created")
+                ,
+                @Header(name = HttpHeaders.LOCATION, schema =
+                        @Schema(type = "string"),
+                        description = "URL of the entity created") }))
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    Response create(@NotNull ApplicationTO applicationTO);
+
+    /**
+     * Updates the application matching the provided key.
+     *
+     * @param applicationTO application to be stored
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @PUT
+    @Path("{key}")
+    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    void update(@NotNull ApplicationTO applicationTO);
+
+    /**
+     * Deletes the application matching the provided key.
+     *
+     * @param key application key to be deleted
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @DELETE
+    @Path("{key}")
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    void delete(@NotNull @PathParam("key") String key);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
index 17ff01e..8b4db6f 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
@@ -61,8 +61,8 @@ public interface UserSelfService extends JAXRSService {
      * @return calling user data, including own UUID and entitlements
      */
     @Operation(security = {
-        @SecurityRequirement(name = "BasicAuthentication")
-        , @SecurityRequirement(name = "Bearer") })
+        @SecurityRequirement(name = "BasicAuthentication"),
+        @SecurityRequirement(name = "Bearer") })
     @ApiResponses(
             @ApiResponse(responseCode = "200", description = "Calling user data, including own UUID and entitlements",
                     content =
@@ -70,8 +70,7 @@ public interface UserSelfService extends JAXRSService {
                             @Schema(implementation = UserTO.class)), headers = {
                 @Header(name = RESTHeaders.RESOURCE_KEY, schema =
                         @Schema(type = "string"),
-                        description = "UUID of the calling user")
-                ,
+                        description = "UUID of the calling user"),
                 @Header(name = RESTHeaders.OWNED_ENTITLEMENTS, schema =
                         @Schema(type = "string"),
                         description = "List of entitlements owned by the calling user")
@@ -101,11 +100,11 @@ public interface UserSelfService extends JAXRSService {
                             @Schema(implementation = ProvisioningResult.class)), headers = {
                 @Header(name = RESTHeaders.RESOURCE_KEY, schema =
                         @Schema(type = "string"),
-                        description = "UUID generated for the user created")
-                , @Header(name = HttpHeaders.LOCATION, schema =
+                        description = "UUID generated for the user created"),
+                @Header(name = HttpHeaders.LOCATION, schema =
                         @Schema(type = "string"),
-                        description = "URL of the user created")
-                , @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
+                        description = "URL of the user created"),
+                @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
                         @Schema(type = "string"),
                         description = "Allows the server to inform the "
                         + "client about the fact that a specified preference was applied") }))
@@ -122,8 +121,8 @@ public interface UserSelfService extends JAXRSService {
      * @return Response object featuring the updated user
      */
     @Operation(security = {
-        @SecurityRequirement(name = "BasicAuthentication")
-        , @SecurityRequirement(name = "Bearer") })
+        @SecurityRequirement(name = "BasicAuthentication"),
+        @SecurityRequirement(name = "Bearer") })
     @Parameter(name = RESTHeaders.PREFER, in = ParameterIn.HEADER,
             description = "Allows client to specify a preference for the result to be returned from the server",
             allowEmptyValue = true, schema =
@@ -133,8 +132,8 @@ public interface UserSelfService extends JAXRSService {
                 description = "User successfully updated enriched with propagation status information, as Entity",
                 content =
                 @Content(schema =
-                        @Schema(implementation = ProvisioningResult.class)))
-        , @ApiResponse(responseCode = "204",
+                        @Schema(implementation = ProvisioningResult.class))),
+        @ApiResponse(responseCode = "204",
                 description = "No content if 'Prefer: return-no-content' was specified", headers =
                 @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
                         @Schema(type = "string"),
@@ -153,8 +152,8 @@ public interface UserSelfService extends JAXRSService {
      * @return Response object featuring the updated user
      */
     @Operation(security = {
-        @SecurityRequirement(name = "BasicAuthentication")
-        , @SecurityRequirement(name = "Bearer") })
+        @SecurityRequirement(name = "BasicAuthentication"),
+        @SecurityRequirement(name = "Bearer") })
     @Parameter(name = RESTHeaders.PREFER, in = ParameterIn.HEADER,
             description = "Allows client to specify a preference for the result to be returned from the server",
             allowEmptyValue = true, schema =
@@ -164,8 +163,8 @@ public interface UserSelfService extends JAXRSService {
                 description = "User successfully updated enriched with propagation status information, as Entity",
                 content =
                 @Content(schema =
-                        @Schema(implementation = ProvisioningResult.class)))
-        , @ApiResponse(responseCode = "204",
+                        @Schema(implementation = ProvisioningResult.class))),
+        @ApiResponse(responseCode = "204",
                 description = "No content if 'Prefer: return-no-content' was specified", headers =
                 @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
                         @Schema(type = "string"),
@@ -184,8 +183,8 @@ public interface UserSelfService extends JAXRSService {
      * @return Response object featuring the updated user enriched with propagation status information
      */
     @Operation(security = {
-        @SecurityRequirement(name = "BasicAuthentication")
-        , @SecurityRequirement(name = "Bearer") })
+        @SecurityRequirement(name = "BasicAuthentication"),
+        @SecurityRequirement(name = "Bearer") })
     @Parameter(name = RESTHeaders.PREFER, in = ParameterIn.HEADER,
             description = "Allows client to specify a preference for the result to be returned from the server",
             allowEmptyValue = true, schema =
@@ -195,8 +194,8 @@ public interface UserSelfService extends JAXRSService {
                 description = "User successfully updated enriched with propagation status information, as Entity",
                 content =
                 @Content(schema =
-                        @Schema(implementation = ProvisioningResult.class)))
-        , @ApiResponse(responseCode = "204",
+                        @Schema(implementation = ProvisioningResult.class))),
+        @ApiResponse(responseCode = "204",
                 description = "No content if 'Prefer: return-no-content' was specified", headers =
                 @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
                         @Schema(type = "string"),
@@ -214,8 +213,8 @@ public interface UserSelfService extends JAXRSService {
      * @return Response object featuring the deleted user
      */
     @Operation(security = {
-        @SecurityRequirement(name = "BasicAuthentication")
-        , @SecurityRequirement(name = "Bearer") })
+        @SecurityRequirement(name = "BasicAuthentication"),
+        @SecurityRequirement(name = "Bearer") })
     @DELETE
     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
     Response delete();
@@ -228,8 +227,8 @@ public interface UserSelfService extends JAXRSService {
      * @return Response object featuring the updated user
      */
     @Operation(security = {
-        @SecurityRequirement(name = "BasicAuthentication")
-        , @SecurityRequirement(name = "Bearer") })
+        @SecurityRequirement(name = "BasicAuthentication"),
+        @SecurityRequirement(name = "Bearer") })
     @POST
     @Path("changePassword")
     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
index 7c4e33a..3bb0148 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
@@ -54,7 +54,8 @@ import org.apache.syncope.common.rest.api.beans.AnyQuery;
 @Tag(name = "Users")
 @SecurityRequirements({
     @SecurityRequirement(name = "BasicAuthentication")
-    , @SecurityRequirement(name = "Bearer") })
+    ,
+    @SecurityRequirement(name = "Bearer") })
 @Path("users")
 public interface UserService extends AnyService<UserTO> {
 
@@ -99,10 +100,12 @@ public interface UserService extends AnyService<UserTO> {
                 @Header(name = RESTHeaders.RESOURCE_KEY, schema =
                         @Schema(type = "string"),
                         description = "UUID generated for the user created")
-                , @Header(name = HttpHeaders.LOCATION, schema =
+                ,
+                @Header(name = HttpHeaders.LOCATION, schema =
                         @Schema(type = "string"),
                         description = "URL of the user created")
-                , @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
+                ,
+                @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
                         @Schema(type = "string"),
                         description = "Allows the server to inform the "
                         + "client about the fact that a specified preference was applied") }))
@@ -139,13 +142,15 @@ public interface UserService extends AnyService<UserTO> {
                 content =
                 @Content(schema =
                         @Schema(implementation = ProvisioningResult.class)))
-        , @ApiResponse(responseCode = "204",
+        ,
+        @ApiResponse(responseCode = "204",
                 description = "No content if 'Prefer: return-no-content' was specified", headers =
                 @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
                         @Schema(type = "string"),
                         description = "Allows the server to inform the "
                         + "client about the fact that a specified preference was applied"))
-        , @ApiResponse(responseCode = "412",
+        ,
+        @ApiResponse(responseCode = "412",
                 description = "The ETag value provided via the 'If-Match' header does not match the latest modification"
                 + " date of the entity") })
     @PATCH
@@ -188,7 +193,8 @@ public interface UserService extends AnyService<UserTO> {
                         @Schema(type = "string"),
                         description = "Allows the server to inform the "
                         + "client about the fact that a specified preference was applied"))
-        , @ApiResponse(responseCode = "412",
+        ,
+        @ApiResponse(responseCode = "412",
                 description = "The ETag value provided via the 'If-Match' header does not match the latest modification"
                 + " date of the entity") })
     @PUT
@@ -224,13 +230,15 @@ public interface UserService extends AnyService<UserTO> {
                 content =
                 @Content(schema =
                         @Schema(implementation = ProvisioningResult.class)))
-        , @ApiResponse(responseCode = "204",
+        ,
+        @ApiResponse(responseCode = "204",
                 description = "No content if 'Prefer: return-no-content' was specified", headers =
                 @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
                         @Schema(type = "string"),
                         description = "Allows the server to inform the "
                         + "client about the fact that a specified preference was applied"))
-        , @ApiResponse(responseCode = "412",
+        ,
+        @ApiResponse(responseCode = "412",
                 description = "The ETag value provided via the 'If-Match' header does not match the latest modification"
                 + " date of the entity") })
     @POST

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/logic/src/main/java/org/apache/syncope/core/logic/ApplicationLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ApplicationLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ApplicationLogic.java
new file mode 100644
index 0000000..e25f532
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ApplicationLogic.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.to.ApplicationTO;
+import org.apache.syncope.common.lib.to.PrivilegeTO;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.core.persistence.api.dao.ApplicationDAO;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.entity.Application;
+import org.apache.syncope.core.persistence.api.entity.Privilege;
+import org.apache.syncope.core.provisioning.api.data.ApplicationDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class ApplicationLogic extends AbstractTransactionalLogic<ApplicationTO> {
+
+    @Autowired
+    private ApplicationDataBinder binder;
+
+    @Autowired
+    private ApplicationDAO applicationDAO;
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.APPLICATION_READ + "')")
+    @Transactional(readOnly = true)
+    public ApplicationTO read(final String key) {
+        Application application = applicationDAO.find(key);
+        if (application == null) {
+            LOG.error("Could not find application '" + key + "'");
+
+            throw new NotFoundException(key);
+        }
+
+        return binder.getApplicationTO(application);
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.APPLICATION_READ + "')")
+    @Transactional(readOnly = true)
+    public PrivilegeTO readPrivilege(final String key) {
+        Privilege privilege = applicationDAO.findPrivilege(key);
+        if (privilege == null) {
+            LOG.error("Could not find privilege '" + key + "'");
+
+            throw new NotFoundException(key);
+        }
+
+        return binder.getPrivilegeTO(privilege);
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.APPLICATION_LIST + "')")
+    @Transactional(readOnly = true)
+    public List<ApplicationTO> list() {
+        return applicationDAO.findAll().stream().
+                map(application -> binder.getApplicationTO(application)).collect(Collectors.toList());
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.APPLICATION_CREATE + "')")
+    public ApplicationTO create(final ApplicationTO applicationTO) {
+        return binder.getApplicationTO(applicationDAO.save(binder.create(applicationTO)));
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.APPLICATION_UPDATE + "')")
+    public ApplicationTO update(final ApplicationTO applicationTO) {
+        Application application = applicationDAO.find(applicationTO.getKey());
+        if (application == null) {
+            LOG.error("Could not find application '" + applicationTO.getKey() + "'");
+            throw new NotFoundException(applicationTO.getKey());
+        }
+
+        return binder.getApplicationTO(applicationDAO.save(binder.update(application, applicationTO)));
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.APPLICATION_DELETE + "')")
+    public ApplicationTO delete(final String key) {
+        Application application = applicationDAO.find(key);
+        if (application == null) {
+            LOG.error("Could not find application '" + key + "'");
+
+            throw new NotFoundException(key);
+        }
+
+        ApplicationTO deleted = binder.getApplicationTO(application);
+        applicationDAO.delete(key);
+        return deleted;
+    }
+
+    @Override
+    protected ApplicationTO 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 ApplicationTO) {
+                    key = ((ApplicationTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if (key != null) {
+            try {
+                return binder.getApplicationTO(applicationDAO.find(key));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
index cfc92f9..d094652 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
@@ -27,7 +27,6 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.patch.BooleanReplacePatchItem;
@@ -81,7 +80,7 @@ public class UserLogic extends AbstractAnyLogic<UserTO, UserPatch> {
     @PreAuthorize("isAuthenticated()")
     @Transactional(readOnly = true)
     public Pair<String, UserTO> selfRead() {
-        return ImmutablePair.of(
+        return Pair.of(
                 POJOHelper.serialize(AuthContextUtils.getAuthorizations()),
                 binder.returnUserTO(binder.getAuthenticatedUserTO()));
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/ApplicationDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/ApplicationDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/ApplicationDAO.java
new file mode 100644
index 0000000..e46667c
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/ApplicationDAO.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.dao;
+
+import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.Application;
+import org.apache.syncope.core.persistence.api.entity.Privilege;
+
+public interface ApplicationDAO extends DAO<Application> {
+
+    Application find(String key);
+
+    Privilege findPrivilege(String key);
+
+    List<Application> findAll();
+
+    Application save(Application application);
+
+    void delete(Application application);
+
+    void delete(String key);
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RoleDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RoleDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RoleDAO.java
index cc29852..0d6aea1 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RoleDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RoleDAO.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.persistence.api.dao;
 
 import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.Privilege;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.Role;
 import org.apache.syncope.core.persistence.api.entity.user.User;
@@ -31,6 +32,8 @@ public interface RoleDAO extends DAO<Role> {
 
     List<Role> findByRealm(Realm realm);
 
+    List<Role> findByPrivilege(Privilege privilege);
+
     List<Role> findAll();
 
     Role save(Role role);

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/PrivilegeCond.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/PrivilegeCond.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/PrivilegeCond.java
new file mode 100644
index 0000000..1647cdb
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/PrivilegeCond.java
@@ -0,0 +1,39 @@
+/*
+ * 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.search;
+
+public class PrivilegeCond extends AbstractSearchCond {
+
+    private static final long serialVersionUID = -8095105031495519762L;
+
+    private String privilege;
+
+    public String getPrivilege() {
+        return privilege;
+    }
+
+    public void setPrivilege(final String privilege) {
+        this.privilege = privilege;
+    }
+
+    @Override
+    public final boolean isValid() {
+        return privilege != null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/SearchCond.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/SearchCond.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/SearchCond.java
index 520fc58..bb1dfa2 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/SearchCond.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/SearchCond.java
@@ -49,6 +49,8 @@ public class SearchCond extends AbstractSearchCond {
 
     private RoleCond roleCond;
 
+    private PrivilegeCond privilegeCond;
+
     private DynRealmCond dynRealmCond;
 
     private ResourceCond resourceCond;
@@ -119,6 +121,15 @@ public class SearchCond extends AbstractSearchCond {
         return nodeCond;
     }
 
+    public static SearchCond getLeafCond(final PrivilegeCond privilegeCond) {
+        SearchCond nodeCond = new SearchCond();
+
+        nodeCond.type = Type.LEAF;
+        nodeCond.privilegeCond = privilegeCond;
+
+        return nodeCond;
+    }
+
     public static SearchCond getLeafCond(final DynRealmCond dynRealmCond) {
         SearchCond nodeCond = new SearchCond();
 
@@ -179,6 +190,12 @@ public class SearchCond extends AbstractSearchCond {
         return nodeCond;
     }
 
+    public static SearchCond getNotLeafCond(final PrivilegeCond privilegeCond) {
+        SearchCond nodeCond = getLeafCond(privilegeCond);
+        nodeCond.type = Type.NOT_LEAF;
+        return nodeCond;
+    }
+
     public static SearchCond getNotLeafCond(final ResourceCond resourceCond) {
         SearchCond nodeCond = getLeafCond(resourceCond);
         nodeCond.type = Type.NOT_LEAF;
@@ -306,6 +323,10 @@ public class SearchCond extends AbstractSearchCond {
         return roleCond;
     }
 
+    public PrivilegeCond getPrivilegeCond() {
+        return privilegeCond;
+    }
+
     public DynRealmCond getDynRealmCond() {
         return dynRealmCond;
     }
@@ -347,12 +368,14 @@ public class SearchCond extends AbstractSearchCond {
             case NOT_LEAF:
                 isValid = (anyTypeCond != null || anyCond != null || attributeCond != null || dynRealmCond != null
                         || relationshipCond != null || relationshipTypeCond != null || membershipCond != null
-                        || roleCond != null || resourceCond != null || assignableCond != null || memberCond != null)
+                        || roleCond != null || privilegeCond != null || resourceCond != null
+                        || assignableCond != null || memberCond != null)
                         && (anyTypeCond == null || anyTypeCond.isValid())
                         && (anyCond == null || anyCond.isValid())
                         && (attributeCond == null || attributeCond.isValid())
                         && (membershipCond == null || membershipCond.isValid())
                         && (roleCond == null || roleCond.isValid())
+                        && (privilegeCond == null || privilegeCond.isValid())
                         && (resourceCond == null || resourceCond.isValid())
                         && (memberCond == null || memberCond.isValid());
                 break;

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Application.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Application.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Application.java
new file mode 100644
index 0000000..525d02a
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Application.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.entity;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface Application extends ProvidedKeyEntity {
+
+    String getDescription();
+
+    void setDescription(String description);
+
+    boolean add(Privilege privilege);
+
+    Optional<? extends Privilege> getPrivilege(String key);
+
+    List<? extends Privilege> getPrivileges();
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Privilege.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Privilege.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Privilege.java
new file mode 100644
index 0000000..44744e6
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Privilege.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+public interface Privilege extends ProvidedKeyEntity {
+
+    Application getApplication();
+
+    void setApplication(Application application);
+
+    String getDescription();
+
+    void setDescription(String description);
+
+    String getSpecMimeType();
+
+    void setSpecMimeType(String specMimeType);
+
+    byte[] getSpec();
+
+    void setSpec(byte[] spec);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Role.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Role.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Role.java
index 68a1e9c..619c228 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Role.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Role.java
@@ -41,4 +41,8 @@ public interface Role extends ProvidedKeyEntity {
     String getConsoleLayoutInfo();
 
     void setConsoleLayoutInfo(String consoleLayoutInfo);
+
+    boolean add(Privilege privilege);
+
+    Set<? extends Privilege> getPrivileges();
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java
index 4c8cd01..103d3d6 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java
@@ -43,6 +43,7 @@ import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond;
 import org.apache.syncope.core.persistence.api.dao.search.AssignableCond;
 import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
 import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
+import org.apache.syncope.core.persistence.api.dao.search.PrivilegeCond;
 import org.apache.syncope.core.persistence.api.dao.search.RelationshipCond;
 import org.apache.syncope.core.persistence.api.dao.search.RelationshipTypeCond;
 
@@ -159,6 +160,12 @@ public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean
                             leaf = SearchCond.getLeafCond(roleCond);
                             break;
 
+                        case PRIVILEGES:
+                            PrivilegeCond privilegeCond = new PrivilegeCond();
+                            privilegeCond.setPrivilege(value);
+                            leaf = SearchCond.getLeafCond(privilegeCond);
+                            break;
+
                         case DYNREALMS:
                             DynRealmCond dynRealmCond = new DynRealmCond();
                             dynRealmCond.setDynRealm(value);

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java b/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java
index 10e229f..8e4946c 100644
--- a/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java
+++ b/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java
@@ -35,6 +35,7 @@ import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond;
 import org.apache.syncope.core.persistence.api.dao.search.AssignableCond;
 import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
 import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
+import org.apache.syncope.core.persistence.api.dao.search.PrivilegeCond;
 import org.apache.syncope.core.persistence.api.dao.search.RelationshipCond;
 import org.apache.syncope.core.persistence.api.dao.search.RelationshipTypeCond;
 import org.junit.jupiter.api.Test;
@@ -199,6 +200,18 @@ public class SearchCondConverterTest {
     }
 
     @Test
+    public void privileges() {
+        String fiql = new UserFiqlSearchConditionBuilder().withPrivileges("postMighty").query();
+        assertEquals(SpecialAttr.PRIVILEGES + "==postMighty", fiql);
+
+        PrivilegeCond privilegeCond = new PrivilegeCond();
+        privilegeCond.setPrivilege("postMighty");
+        SearchCond simpleCond = SearchCond.getLeafCond(privilegeCond);
+
+        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+    }
+
+    @Test
     public void dynRealms() {
         String dynRealm = UUID.randomUUID().toString();
         String fiql = new UserFiqlSearchConditionBuilder().inDynRealms(dynRealm).query();

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentLoader.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentLoader.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentLoader.java
index 3dc5a05..0211404 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentLoader.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentLoader.java
@@ -108,7 +108,7 @@ public class XMLContentLoader extends AbstractContentDealer implements ContentLo
         JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
 
         Properties views = PropertiesLoaderUtils.loadProperties(viewsXML.getResource());
-        views.stringPropertyNames().stream().forEach(idx -> {
+        views.stringPropertyNames().stream().sorted().forEachOrdered(idx -> {
             LOG.debug("[{}] Creating view {}", domain, views.get(idx).toString());
             try {
                 jdbcTemplate.execute(views.get(idx).toString().replaceAll("\\n", " "));
@@ -126,7 +126,7 @@ public class XMLContentLoader extends AbstractContentDealer implements ContentLo
         JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
 
         Properties indexes = PropertiesLoaderUtils.loadProperties(indexesXML.getResource());
-        indexes.stringPropertyNames().stream().forEach(idx -> {
+        indexes.stringPropertyNames().stream().sorted().forEachOrdered(idx -> {
             LOG.debug("[{}] Creating index {}", domain, indexes.get(idx).toString());
             try {
                 jdbcTemplate.execute(indexes.get(idx).toString());

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
index 7ffd176..7d0ba9f 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
@@ -46,6 +46,7 @@ import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond;
 import org.apache.syncope.core.persistence.api.dao.search.AssignableCond;
 import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
 import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
+import org.apache.syncope.core.persistence.api.dao.search.PrivilegeCond;
 import org.apache.syncope.core.persistence.api.dao.search.RelationshipCond;
 import org.apache.syncope.core.persistence.api.dao.search.RelationshipTypeCond;
 import org.apache.syncope.core.persistence.api.entity.Any;
@@ -70,7 +71,7 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
 
         Set<String> realmKeys = new HashSet<>();
         Set<String> dynRealmKeys = new HashSet<>();
-        for (String realmPath : RealmUtils.normalize(adminRealms)) {
+        RealmUtils.normalize(adminRealms).forEach(realmPath -> {
             if (realmPath.startsWith("/")) {
                 Realm realm = realmDAO.findByFullPath(realmPath);
                 if (realm == null) {
@@ -89,7 +90,7 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
                     dynRealmKeys.add(dynRealm.getKey());
                 }
             }
-        }
+        });
         if (!dynRealmKeys.isEmpty()) {
             realmKeys.addAll(realmDAO.findAll().stream().
                     map(r -> r.getKey()).collect(Collectors.toSet()));
@@ -366,6 +367,9 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
                 } else if (cond.getRoleCond() != null && AnyTypeKind.USER == svs.anyTypeKind) {
                     query.append(getQuery(cond.getRoleCond(),
                             cond.getType() == SearchCond.Type.NOT_LEAF, parameters, svs));
+                } else if (cond.getPrivilegeCond() != null && AnyTypeKind.USER == svs.anyTypeKind) {
+                    query.append(getQuery(cond.getPrivilegeCond(),
+                            cond.getType() == SearchCond.Type.NOT_LEAF, parameters, svs));
                 } else if (cond.getDynRealmCond() != null) {
                     query.append(getQuery(cond.getDynRealmCond(),
                             cond.getType() == SearchCond.Type.NOT_LEAF, parameters, svs));
@@ -550,6 +554,37 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     private String getQuery(
+            final PrivilegeCond cond, final boolean not, final List<Object> parameters, final SearchSupport svs) {
+
+        StringBuilder query = new StringBuilder("SELECT DISTINCT any_id FROM ").
+                append(svs.field().name).append(" WHERE (");
+
+        if (not) {
+            query.append("any_id NOT IN (");
+        } else {
+            query.append("any_id IN (");
+        }
+
+        query.append("SELECT DISTINCT any_id FROM ").
+                append(svs.priv().name).append(" WHERE ").
+                append("privilege_id=?").append(setParameter(parameters, cond.getPrivilege())).
+                append(") ");
+
+        if (not) {
+            query.append("AND any_id NOT IN (");
+        } else {
+            query.append("OR any_id IN (");
+        }
+
+        query.append("SELECT DISTINCT any_id FROM ").
+                append(svs.dynpriv().name).append(" WHERE ").
+                append("privilege_id=?").append(setParameter(parameters, cond.getPrivilege())).
+                append("))");
+
+        return query.toString();
+    }
+
+    private String getQuery(
             final DynRealmCond cond, final boolean not, final List<Object> parameters, final SearchSupport svs) {
 
         StringBuilder query = new StringBuilder("SELECT DISTINCT any_id FROM ").
@@ -609,9 +644,9 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
         StringBuilder query = new StringBuilder("SELECT DISTINCT any_id FROM ").
                 append(svs.field().name).append(" WHERE (");
         if (cond.isFromGroup()) {
-            for (Realm current : realmDAO.findDescendants(realm)) {
+            realmDAO.findDescendants(realm).forEach(current -> {
                 query.append("realm_id=?").append(setParameter(parameters, current.getKey())).append(" OR ");
-            }
+            });
             query.setLength(query.length() - 4);
         } else {
             for (Realm current = realm; current.getParent() != null; current = current.getParent()) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAApplicationDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAApplicationDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAApplicationDAO.java
new file mode 100644
index 0000000..dd6958d
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAApplicationDAO.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.dao;
+
+import java.util.List;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.core.persistence.api.dao.ApplicationDAO;
+import org.apache.syncope.core.persistence.api.dao.RoleDAO;
+import org.apache.syncope.core.persistence.api.entity.Application;
+import org.apache.syncope.core.persistence.api.entity.Privilege;
+import org.apache.syncope.core.persistence.jpa.entity.JPAApplication;
+import org.apache.syncope.core.persistence.jpa.entity.JPAPrivilege;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class JPAApplicationDAO extends AbstractDAO<Application> implements ApplicationDAO {
+
+    @Autowired
+    private RoleDAO roleDAO;
+
+    @Override
+    public Application find(final String key) {
+        return entityManager().find(JPAApplication.class, key);
+    }
+
+    @Override
+    public Privilege findPrivilege(final String key) {
+        return entityManager().find(JPAPrivilege.class, key);
+    }
+
+    @Override
+    public List<Application> findAll() {
+        TypedQuery<Application> query = entityManager().createQuery(
+                "SELECT e FROM " + JPAApplication.class.getSimpleName() + " e ", Application.class);
+        return query.getResultList();
+    }
+
+    @Override
+    public Application save(final Application application) {
+        return entityManager().merge(application);
+    }
+
+    @Override
+    public void delete(final Application application) {
+        application.getPrivileges().forEach(privilege -> {
+            roleDAO.findByPrivilege(privilege).forEach(role -> {
+                role.getPrivileges().remove(privilege);
+            });
+
+            privilege.setApplication(null);
+        });
+        application.getPrivileges().clear();
+
+        entityManager().remove(application);
+    }
+
+    @Override
+    public void delete(final String key) {
+        Application application = find(key);
+        if (application == null) {
+            return;
+        }
+
+        delete(application);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
index 367ee8d..dda23b2 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
@@ -27,6 +27,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.entity.Privilege;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.Role;
 import org.apache.syncope.core.persistence.api.entity.user.User;
@@ -80,6 +81,15 @@ public class JPARoleDAO extends AbstractDAO<Role> implements RoleDAO {
     }
 
     @Override
+    public List<Role> findByPrivilege(final Privilege privilege) {
+        TypedQuery<Role> query = entityManager().createQuery(
+                "SELECT e FROM " + JPARole.class.getSimpleName() + " e WHERE :privilege MEMBER OF e.privileges",
+                Role.class);
+        query.setParameter("privilege", privilege);
+        return query.getResultList();
+    }
+
+    @Override
     public List<Role> findAll() {
         TypedQuery<Role> query = entityManager().createQuery(
                 "SELECT e FROM " + JPARole.class.getSimpleName() + " e ", Role.class);
@@ -97,14 +107,14 @@ public class JPARoleDAO extends AbstractDAO<Role> implements RoleDAO {
 
             clearDynMembers(merged);
 
-            for (User user : matching) {
+            matching.forEach((user) -> {
                 Query insert = entityManager().createNativeQuery("INSERT INTO " + DYNMEMB_TABLE + " VALUES(?, ?)");
                 insert.setParameter(1, user.getKey());
                 insert.setParameter(2, merged.getKey());
                 insert.executeUpdate();
 
                 publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, user, AuthContextUtils.getDomain()));
-            }
+            });
         }
 
         return merged;
@@ -116,10 +126,10 @@ public class JPARoleDAO extends AbstractDAO<Role> implements RoleDAO {
                 "SELECT e FROM " + JPAUser.class.getSimpleName() + " e WHERE :role MEMBER OF e.roles", User.class);
         query.setParameter("role", role);
 
-        for (User user : query.getResultList()) {
+        query.getResultList().forEach(user -> {
             user.getRoles().remove(role);
             publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, user, AuthContextUtils.getDomain()));
-        }
+        });
 
         clearDynMembers(role);
 
@@ -166,22 +176,20 @@ public class JPARoleDAO extends AbstractDAO<Role> implements RoleDAO {
     @Transactional
     @Override
     public void refreshDynMemberships(final User user) {
-        for (Role role : findAll()) {
-            if (role.getDynMembership() != null) {
-                Query delete = entityManager().createNativeQuery(
-                        "DELETE FROM " + DYNMEMB_TABLE + " WHERE role_id=? AND any_id=?");
-                delete.setParameter(1, role.getKey());
-                delete.setParameter(2, user.getKey());
-                delete.executeUpdate();
-
-                if (searchDAO().matches(user, SearchCondConverter.convert(role.getDynMembership().getFIQLCond()))) {
-                    Query insert = entityManager().createNativeQuery("INSERT INTO " + DYNMEMB_TABLE + " VALUES(?, ?)");
-                    insert.setParameter(1, user.getKey());
-                    insert.setParameter(2, role.getKey());
-                    insert.executeUpdate();
-                }
+        findAll().stream().filter(role -> role.getDynMembership() != null).forEach(role -> {
+            Query delete = entityManager().createNativeQuery(
+                    "DELETE FROM " + DYNMEMB_TABLE + " WHERE role_id=? AND any_id=?");
+            delete.setParameter(1, role.getKey());
+            delete.setParameter(2, user.getKey());
+            delete.executeUpdate();
+
+            if (searchDAO().matches(user, SearchCondConverter.convert(role.getDynMembership().getFIQLCond()))) {
+                Query insert = entityManager().createNativeQuery("INSERT INTO " + DYNMEMB_TABLE + " VALUES(?, ?)");
+                insert.setParameter(1, user.getKey());
+                insert.setParameter(2, role.getKey());
+                insert.executeUpdate();
             }
-        }
+        });
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/SearchSupport.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/SearchSupport.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/SearchSupport.java
index 5cac5bb..aa65aea 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/SearchSupport.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/SearchSupport.java
@@ -131,6 +131,14 @@ class SearchSupport {
         return new SearchView("svr", field().name + "_role");
     }
 
+    public SearchView priv() {
+        return new SearchView("svp", field().name + "_priv");
+    }
+
+    public SearchView dynpriv() {
+        return new SearchView("svdp", field().name + "_dynpriv");
+    }
+
     public SearchView dynrolemembership() {
         return new SearchView("svdr", JPARoleDAO.DYNMEMB_TABLE);
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAApplication.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAApplication.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAApplication.java
new file mode 100644
index 0000000..0b5f093
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAApplication.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import org.apache.syncope.core.persistence.api.entity.Application;
+import org.apache.syncope.core.persistence.api.entity.Privilege;
+
+@Entity
+@Table(name = JPAApplication.TABLE)
+public class JPAApplication extends AbstractProvidedKeyEntity implements Application {
+
+    private static final long serialVersionUID = -5951400197744722305L;
+
+    public static final String TABLE = "Application";
+
+    private String description;
+
+    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "application")
+    private List<JPAPrivilege> privileges = new ArrayList<>();
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    @Override
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    @Override
+    public boolean add(final Privilege privilege) {
+        checkType(privilege, JPAPrivilege.class);
+        return privileges.contains((JPAPrivilege) privilege) || privileges.add((JPAPrivilege) privilege);
+    }
+
+    @Override
+    public Optional<? extends Privilege> getPrivilege(final String key) {
+        return privileges.stream().filter(privilege -> privilege.getKey().equals(key)).findFirst();
+    }
+
+    @Override
+    public List<? extends Privilege> getPrivileges() {
+        return privileges;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
----------------------------------------------------------------------
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 fe3a037..aa2809a 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
@@ -30,6 +30,7 @@ import org.apache.syncope.core.persistence.api.entity.AnyAbout;
 import org.apache.syncope.core.persistence.api.entity.AnyTemplateRealm;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
+import org.apache.syncope.core.persistence.api.entity.Application;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.apache.syncope.core.persistence.api.entity.ConnInstanceHistoryConf;
 import org.apache.syncope.core.persistence.api.entity.ConnPoolConf;
@@ -128,6 +129,7 @@ import org.apache.syncope.core.persistence.jpa.entity.resource.JPAOrgUnit;
 import org.apache.syncope.core.persistence.api.entity.DynRealm;
 import org.apache.syncope.core.persistence.api.entity.DynRealmMembership;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.Privilege;
 import org.apache.syncope.core.persistence.api.entity.policy.CorrelationRule;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResourceHistoryConf;
 import org.apache.syncope.core.persistence.api.entity.resource.OrgUnitItem;
@@ -171,6 +173,10 @@ public class JPAEntityFactory implements EntityFactory {
             result = (E) new JPAAnyObject();
         } else if (reference.equals(Role.class)) {
             result = (E) new JPARole();
+        } else if (reference.equals(Application.class)) {
+            result = (E) new JPAApplication();
+        } else if (reference.equals(Privilege.class)) {
+            result = (E) new JPAPrivilege();
         } else if (reference.equals(User.class)) {
             result = (E) new JPAUser();
         } else if (reference.equals(Group.class)) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/425f9b9e/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAPrivilege.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAPrivilege.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAPrivilege.java
new file mode 100644
index 0000000..a01bb5f
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAPrivilege.java
@@ -0,0 +1,90 @@
+/*
+ * 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;
+
+import javax.persistence.Entity;
+import javax.persistence.Lob;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.core.persistence.api.entity.Application;
+import org.apache.syncope.core.persistence.api.entity.Privilege;
+
+@Entity
+@Table(name = JPAPrivilege.TABLE)
+public class JPAPrivilege extends AbstractProvidedKeyEntity implements Privilege {
+
+    private static final long serialVersionUID = -6479069294944858456L;
+
+    public static final String TABLE = "Privilege";
+
+    @ManyToOne
+    private JPAApplication application;
+
+    private String description;
+
+    @NotNull
+    private String specMimeType;
+
+    @Lob
+    private byte[] spec;
+
+    @Override
+    public Application getApplication() {
+        return application;
+    }
+
+    @Override
+    public void setApplication(final Application application) {
+        checkType(application, JPAApplication.class);
+        this.application = (JPAApplication) application;
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    @Override
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    @Override
+    public String getSpecMimeType() {
+        return specMimeType;
+    }
+
+    @Override
+    public void setSpecMimeType(final String specMimeType) {
+        this.specMimeType = specMimeType;
+    }
+
+    @Override
+    public byte[] getSpec() {
+        return spec;
+    }
+
+    @Override
+    public void setSpec(final byte[] spec) {
+        this.spec = ArrayUtils.clone(spec);
+    }
+
+}


Mime
View raw message