config) {
+ this.config = config;
+ }
+
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getDescription() {
+ return this.description;
+ }
+
+ @Override
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public ResourceServerEntity getResourceServer() {
+ return this.resourceServer;
+ }
+
+ public void setResourceServer(ResourceServerEntity resourceServer) {
+ this.resourceServer = resourceServer;
+ }
+
+ @Override
+ public Set
getAssociatedPolicies() {
+ return (Set
) this.associatedPolicies;
+ }
+
+ public void setAssociatedPolicies(Set associatedPolicies) {
+ this.associatedPolicies = associatedPolicies;
+ }
+
+ @Override
+ public Set getResources() {
+ return this.resources;
+ }
+
+ public void setResources(Set resources) {
+ this.resources = resources;
+ }
+
+ @Override
+ public Set getScopes() {
+ return this.scopes;
+ }
+
+ public void setScopes(Set scopes) {
+ this.scopes = scopes;
+ }
+
+ @Override
+ public void addScope(Scope scope) {
+ getScopes().add((ScopeEntity) scope);
+ }
+
+ @Override
+ public void removeScope(Scope scope) {
+ getScopes().remove(scope);
+ }
+
+ @Override
+ public void addAssociatedPolicy(Policy associatedPolicy) {
+ getAssociatedPolicies().add(associatedPolicy);
+ }
+
+ @Override
+ public void removeAssociatedPolicy(Policy associatedPolicy) {
+ getAssociatedPolicies().remove(associatedPolicy);
+ }
+
+ @Override
+ public void addResource(Resource resource) {
+ getResources().add((ResourceEntity) resource);
+ }
+
+ @Override
+ public void removeResource(Resource resource) {
+ getResources().remove(resource);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+
+ if (this.id == null) return false;
+
+ if (o == null || getClass() != o.getClass()) return false;
+
+ AbstractIdentifiableEntity that = (AbstractIdentifiableEntity) o;
+
+ if (!getId().equals(that.getId())) return false;
+
+ return true;
+
+ }
+
+ @Override
+ public int hashCode() {
+ return id!=null ? id.hashCode() : super.hashCode();
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceEntity.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceEntity.java
new file mode 100644
index 00000000000..7cb1a6f565e
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceEntity.java
@@ -0,0 +1,190 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.jpa.entities;
+
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.Scope;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Pedro Igor
+ */
+@Entity
+@Table(name = "RESOURCE_SERVER_RESOURCE", uniqueConstraints = {
+ @UniqueConstraint(columnNames = {"NAME", "RESOURCE_SERVER_ID", "OWNER"})
+})
+public class ResourceEntity implements Resource {
+
+ @Id
+ @Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
+ private String id;
+
+ @Column(name = "NAME")
+ private String name;
+
+ @Column(name = "URI")
+ private String uri;
+
+ @Column(name = "TYPE")
+ private String type;
+
+ @Column(name = "ICON_URI")
+ private String iconUri;
+
+ @Column(name = "OWNER")
+ private String owner;
+
+ @ManyToOne(optional = false)
+ @JoinColumn(name = "RESOURCE_SERVER_ID")
+ private ResourceServerEntity resourceServer;
+
+ @ManyToMany(fetch = FetchType.LAZY, cascade = {})
+ @JoinTable(name = "RESOURCE_SCOPE", joinColumns = @JoinColumn(name = "RESOURCE_ID"), inverseJoinColumns = @JoinColumn(name = "SCOPE_ID"))
+ private List scopes = new ArrayList<>();
+
+ @ManyToMany(fetch = FetchType.LAZY, cascade = {})
+ @JoinTable(name = "RESOURCE_POLICY", joinColumns = @JoinColumn(name = "RESOURCE_ID"), inverseJoinColumns = @JoinColumn(name = "POLICY_ID"))
+ private List policies = new ArrayList<>();
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getUri() {
+ return uri;
+ }
+
+ @Override
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ @Override
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ @Override
+ public List getScopes() {
+ return this.scopes;
+ }
+
+ @Override
+ public String getIconUri() {
+ return iconUri;
+ }
+
+ @Override
+ public void setIconUri(String iconUri) {
+ this.iconUri = iconUri;
+ }
+
+ @Override
+ public ResourceServerEntity getResourceServer() {
+ return resourceServer;
+ }
+
+ public void setResourceServer(ResourceServerEntity resourceServer) {
+ this.resourceServer = resourceServer;
+ }
+
+ public String getOwner() {
+ return this.owner;
+ }
+
+ public void setOwner(String owner) {
+ this.owner = owner;
+ }
+
+ public List getPolicies() {
+ return this.policies;
+ }
+
+ public void updateScopes(Set toUpdate) {
+ for (Scope scope : toUpdate) {
+ boolean hasScope = false;
+
+ for (Scope existingScope : this.scopes) {
+ if (existingScope.equals(scope)) {
+ hasScope = true;
+ }
+ }
+
+ if (!hasScope) {
+ this.scopes.add((ScopeEntity) scope);
+ }
+ }
+
+ for (Scope scopeModel : new HashSet(this.scopes)) {
+ boolean hasScope = false;
+
+ for (Scope scope : toUpdate) {
+ if (scopeModel.equals(scope)) {
+ hasScope = true;
+ }
+ }
+
+ if (!hasScope) {
+ this.scopes.remove(scopeModel);
+ }
+ }
+ }
+
+ public void setPolicies(List policies) {
+ this.policies = policies;
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceServerEntity.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceServerEntity.java
new file mode 100644
index 00000000000..b74b2310673
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceServerEntity.java
@@ -0,0 +1,113 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.jpa.entities;
+
+import org.keycloak.authorization.model.ResourceServer;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import java.util.List;
+
+/**
+ * @author Pedro Igor
+ */
+@Entity
+@Table(name = "RESOURCE_SERVER", uniqueConstraints = {@UniqueConstraint(columnNames = "CLIENT_ID")})
+public class ResourceServerEntity implements ResourceServer {
+
+ @Id
+ @Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
+ private String id;
+
+ @Column(name = "CLIENT_ID")
+ private String clientId;
+
+ @Column(name = "ALLOW_RS_REMOTE_MGMT")
+ private boolean allowRemoteResourceManagement;
+
+ @Column(name = "POLICY_ENFORCE_MODE")
+ private PolicyEnforcementMode policyEnforcementMode = PolicyEnforcementMode.ENFORCING;
+
+ @OneToMany(mappedBy = "resourceServer")
+ private List resources;
+
+ @OneToMany (mappedBy = "resourceServer")
+ private List scopes;
+
+ @Override
+ public String getId() {
+ return this.id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String getClientId() {
+ return this.clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ @Override
+ public boolean isAllowRemoteResourceManagement() {
+ return this.allowRemoteResourceManagement;
+ }
+
+ @Override
+ public void setAllowRemoteResourceManagement(boolean allowRemoteResourceManagement) {
+ this.allowRemoteResourceManagement = allowRemoteResourceManagement;
+ }
+
+ @Override
+ public PolicyEnforcementMode getPolicyEnforcementMode() {
+ return this.policyEnforcementMode;
+ }
+
+ @Override
+ public void setPolicyEnforcementMode(PolicyEnforcementMode policyEnforcementMode) {
+ this.policyEnforcementMode = policyEnforcementMode;
+ }
+
+ public List getResources() {
+ return this.resources;
+ }
+
+ public void setResources(final List resources) {
+ this.resources = resources;
+ }
+
+ public List getScopes() {
+ return this.scopes;
+ }
+
+ public void setScopes(final List scopes) {
+ this.scopes = scopes;
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ScopeEntity.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ScopeEntity.java
new file mode 100644
index 00000000000..99f8b41edb8
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ScopeEntity.java
@@ -0,0 +1,126 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.jpa.entities;
+
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Scope;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @author Pedro Igor
+ */
+@Entity
+@Table(name = "RESOURCE_SERVER_SCOPE", uniqueConstraints = {
+ @UniqueConstraint(columnNames = {"NAME", "RESOURCE_SERVER_ID"})
+})
+public class ScopeEntity implements Scope {
+
+ @Id
+ @Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
+ private String id;
+
+ @Column(name = "NAME")
+ private String name;
+
+ @Column(name = "ICON_URI")
+ private String iconUri;
+
+ @ManyToOne(optional = false)
+ @JoinColumn(name = "RESOURCE_SERVER_ID")
+ private ResourceServerEntity resourceServer;
+
+ @ManyToMany(fetch = FetchType.LAZY, cascade = {})
+ @JoinTable(name = "SCOPE_POLICY", joinColumns = @JoinColumn(name = "SCOPE_ID"), inverseJoinColumns = @JoinColumn(name = "POLICY_ID"))
+ private List policies = new ArrayList<>();
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getIconUri() {
+ return iconUri;
+ }
+
+ @Override
+ public void setIconUri(String iconUri) {
+ this.iconUri = iconUri;
+ }
+
+ @Override
+ public ResourceServerEntity getResourceServer() {
+ return resourceServer;
+ }
+
+ public List extends Policy> getPolicies() {
+ return this.policies;
+ }
+
+ public void setPolicies(List policies) {
+ this.policies = policies;
+ }
+
+ public void setResourceServer(final ResourceServerEntity resourceServer) {
+ this.resourceServer = resourceServer;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ScopeEntity that = (ScopeEntity) o;
+ return Objects.equals(id, that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAAuthorizationStoreFactory.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAAuthorizationStoreFactory.java
new file mode 100644
index 00000000000..87888848ee6
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAAuthorizationStoreFactory.java
@@ -0,0 +1,56 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.jpa.store;
+
+import org.keycloak.Config;
+import org.keycloak.authorization.store.AuthorizationStoreFactory;
+import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.connections.jpa.JpaConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+
+import javax.persistence.EntityManager;
+
+/**
+ * @author Pedro Igor
+ */
+public class JPAAuthorizationStoreFactory implements AuthorizationStoreFactory {
+ @Override
+ public StoreFactory create(KeycloakSession session) {
+ return new JPAStoreFactory(getEntityManager(session));
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return "jpa";
+ }
+
+ private EntityManager getEntityManager(KeycloakSession session) {
+ return session.getProvider(JpaConnectionProvider.class).getEntityManager();
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java
new file mode 100644
index 00000000000..b05f933e92a
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java
@@ -0,0 +1,157 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.jpa.store;
+
+import org.keycloak.authorization.jpa.entities.PolicyEntity;
+import org.keycloak.authorization.jpa.entities.ResourceServerEntity;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.store.PolicyStore;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+import javax.persistence.Query;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Pedro Igor
+ */
+public class JPAPolicyStore implements PolicyStore {
+
+ private final EntityManager entityManager;
+
+ public JPAPolicyStore(EntityManager entityManager) {
+ this.entityManager = entityManager;
+ }
+
+ @Override
+ public Policy create(String name, String type, ResourceServer resourceServer) {
+ PolicyEntity entity = new PolicyEntity();
+
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setName(name);
+ entity.setType(type);
+ entity.setResourceServer((ResourceServerEntity) resourceServer);
+
+ this.entityManager.persist(entity);
+
+ return entity;
+ }
+
+ public EntityManager getEntityManager() {
+ return this.entityManager;
+ }
+
+ @Override
+ public void delete(String id) {
+ Policy policy = findById(id);
+
+ if (policy != null) {
+ getEntityManager().remove(policy);
+ }
+ }
+
+
+ @Override
+ public Policy findById(String id) {
+ return getEntityManager().find(PolicyEntity.class, id);
+ }
+
+ @Override
+ public Policy findByName(String name, String resourceServerId) {
+ try {
+ Query query = getEntityManager().createQuery("from PolicyEntity where name = :name and resourceServer.id = :serverId");
+
+ query.setParameter("name", name);
+ query.setParameter("serverId", resourceServerId);
+
+ return (Policy) query.getSingleResult();
+ } catch (NoResultException nre) {
+ return null;
+ }
+ }
+
+ @Override
+ public List findByResourceServer(final String resourceServerId) {
+ Query query = getEntityManager().createQuery("from PolicyEntity where resourceServer.id = :serverId");
+
+ query.setParameter("serverId", resourceServerId);
+
+ return query.getResultList();
+ }
+
+ @Override
+ public List findByResource(final String resourceId) {
+ Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.resources r where r.id = :resourceId");
+
+ query.setParameter("resourceId", resourceId);
+
+ return query.getResultList();
+ }
+
+ @Override
+ public List findByResourceType(final String resourceType, String resourceServerId) {
+ List policies = new ArrayList<>();
+ Query query = getEntityManager().createQuery("from PolicyEntity where resourceServer.id = :serverId");
+
+ query.setParameter("serverId", resourceServerId);
+
+ List models = query.getResultList();
+
+ for (Policy policy : models) {
+ String defaultType = policy.getConfig().get("defaultResourceType");
+
+ if (defaultType != null && defaultType.equals(resourceType) && policy.getResources().isEmpty()) {
+ policies.add(policy);
+ }
+ }
+
+ return policies;
+ }
+
+ @Override
+ public List findByScopeIds(List scopeIds, String resourceServerId) {
+ Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.scopes s where p.resourceServer.id = :serverId and s.id in (:scopeIds) and p.resources is empty group by p.id order by p.name");
+
+ query.setParameter("serverId", resourceServerId);
+ query.setParameter("scopeIds", scopeIds);
+
+ return query.getResultList();
+ }
+
+ @Override
+ public List findByType(String type) {
+ Query query = getEntityManager().createQuery("select p from PolicyEntity p where p.type = :type");
+
+ query.setParameter("type", type);
+
+ return query.getResultList();
+ }
+
+ @Override
+ public List findDependentPolicies(String policyId) {
+ Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.associatedPolicies ap where ap.id in (:policyId)");
+
+ query.setParameter("policyId", Arrays.asList(policyId));
+
+ return query.getResultList();
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceServerStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceServerStore.java
new file mode 100644
index 00000000000..51d0369245f
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceServerStore.java
@@ -0,0 +1,75 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.jpa.store;
+
+import org.keycloak.authorization.jpa.entities.ResourceServerEntity;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.store.ResourceServerStore;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+import java.util.List;
+
+/**
+ * @author Pedro Igor
+ */
+public class JPAResourceServerStore implements ResourceServerStore {
+
+ private final EntityManager entityManager;
+
+ public JPAResourceServerStore(EntityManager entityManager) {
+ this.entityManager = entityManager;
+ }
+
+ @Override
+ public ResourceServer create(String clientId) {
+ ResourceServerEntity entity = new ResourceServerEntity();
+
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setClientId(clientId);
+
+ this.entityManager.persist(entity);
+
+ return entity;
+ }
+
+ @Override
+ public void delete(String id) {
+ this.entityManager.remove(findById(id));
+ }
+
+ @Override
+ public ResourceServer findById(String id) {
+ return entityManager.find(ResourceServerEntity.class, id);
+ }
+
+ @Override
+ public ResourceServer findByClient(final String clientId) {
+ Query query = entityManager.createQuery("from ResourceServerEntity where clientId = :clientId");
+
+ query.setParameter("clientId", clientId);
+ List result = query.getResultList();
+
+ if (result.isEmpty()) {
+ return null;
+ }
+
+ return (ResourceServer) result.get(0);
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
new file mode 100644
index 00000000000..802989c9735
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
@@ -0,0 +1,132 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.jpa.store;
+
+import org.keycloak.authorization.jpa.entities.ResourceEntity;
+import org.keycloak.authorization.jpa.entities.ResourceServerEntity;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.store.ResourceStore;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Pedro Igor
+ */
+public class JPAResourceStore implements ResourceStore {
+
+ private final EntityManager entityManager;
+
+ public JPAResourceStore(EntityManager entityManager) {
+ this.entityManager = entityManager;
+ }
+
+ @Override
+ public Resource create(String name, ResourceServer resourceServer, String owner) {
+ if (!(resourceServer instanceof ResourceServerEntity)) {
+ throw new RuntimeException("Unexpected type [" + resourceServer.getClass() + "].");
+ }
+
+ ResourceEntity entity = new ResourceEntity();
+
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setName(name);
+ entity.setResourceServer((ResourceServerEntity) resourceServer);
+ entity.setOwner(owner);
+
+ this.entityManager.persist(entity);
+
+ return entity;
+ }
+
+ @Override
+ public void delete(String id) {
+ Resource resource = findById(id);
+
+ resource.getScopes().clear();
+
+ if (resource != null) {
+ this.entityManager.remove(resource);
+ }
+ }
+
+ @Override
+ public Resource findById(String id) {
+ if (id == null) {
+ return null;
+ }
+
+ return entityManager.find(ResourceEntity.class, id);
+ }
+
+ @Override
+ public List findByOwner(String ownerId) {
+ Query query = entityManager.createQuery("from ResourceEntity where owner = :ownerId");
+
+ query.setParameter("ownerId", ownerId);
+
+ return query.getResultList();
+ }
+
+ @Override
+ public List findByResourceServer(String resourceServerId) {
+ Query query = entityManager.createQuery("from ResourceEntity where resourceServer.id = :serverId");
+
+ query.setParameter("serverId", resourceServerId);
+
+ return query.getResultList();
+ }
+
+ @Override
+ public List findByScope(String... id) {
+ Query query = entityManager.createQuery("from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)");
+
+ query.setParameter("scopeIds", Arrays.asList(id));
+
+ return query.getResultList();
+ }
+
+ @Override
+ public Resource findByName(String name, String resourceServerId) {
+ Query query = entityManager.createQuery("from ResourceEntity where resourceServer.id = :serverId and name = :name");
+
+ query.setParameter("serverId", resourceServerId);
+ query.setParameter("name", name);
+
+ List result = query.getResultList();
+
+ if (!result.isEmpty()) {
+ return result.get(0);
+ }
+
+ return null;
+ }
+
+ @Override
+ public List findByType(String type) {
+ Query query = entityManager.createQuery("from ResourceEntity where type = :type");
+
+ query.setParameter("type", type);
+
+ return query.getResultList();
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java
new file mode 100644
index 00000000000..cc9a956498b
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java
@@ -0,0 +1,88 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.jpa.store;
+
+import org.keycloak.authorization.jpa.entities.ResourceServerEntity;
+import org.keycloak.authorization.jpa.entities.ScopeEntity;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
+import org.keycloak.authorization.store.ScopeStore;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+import javax.persistence.Query;
+import java.util.List;
+
+/**
+ * @author Pedro Igor
+ */
+public class JPAScopeStore implements ScopeStore {
+
+ private final EntityManager entityManager;
+
+ public JPAScopeStore(EntityManager entityManager) {
+ this.entityManager = entityManager;
+ }
+
+ @Override
+ public Scope create(final String name, final ResourceServer resourceServer) {
+ ScopeEntity entity = new ScopeEntity();
+
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setName(name);
+ entity.setResourceServer((ResourceServerEntity) resourceServer);
+
+ this.entityManager.persist(entity);
+
+ return entity;
+ }
+
+ @Override
+ public void delete(String id) {
+ this.entityManager.remove(findById(id));
+ }
+
+ @Override
+ public Scope findById(String id) {
+ return entityManager.find(ScopeEntity.class, id);
+ }
+
+ @Override
+ public Scope findByName(String name, String resourceServerId) {
+ try {
+ Query query = entityManager.createQuery("select s from ScopeEntity s inner join s.resourceServer rs where rs.id = :resourceServerId and name = :name");
+
+ query.setParameter("name", name);
+ query.setParameter("resourceServerId", resourceServerId);
+
+ return (Scope) query.getSingleResult();
+ } catch (NoResultException nre) {
+ return null;
+ }
+ }
+
+ @Override
+ public List findByResourceServer(final String serverId) {
+ Query query = entityManager.createQuery("from ScopeEntity where resourceServer.id = :serverId");
+
+ query.setParameter("serverId", serverId);
+
+ return query.getResultList();
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAStoreFactory.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAStoreFactory.java
new file mode 100644
index 00000000000..5dad6af60b6
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAStoreFactory.java
@@ -0,0 +1,64 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.jpa.store;
+
+import org.keycloak.authorization.store.PolicyStore;
+import org.keycloak.authorization.store.ResourceServerStore;
+import org.keycloak.authorization.store.ResourceStore;
+import org.keycloak.authorization.store.ScopeStore;
+import org.keycloak.authorization.store.StoreFactory;
+
+import javax.persistence.EntityManager;
+
+/**
+ * @author Pedro Igor
+ */
+public class JPAStoreFactory implements StoreFactory {
+
+ private final EntityManager entityManager;
+
+ public JPAStoreFactory(EntityManager entityManager) {
+ this.entityManager = entityManager;
+ }
+
+ @Override
+ public PolicyStore getPolicyStore() {
+ return new JPAPolicyStore(this.entityManager);
+ }
+
+ @Override
+ public ResourceServerStore getResourceServerStore() {
+ return new JPAResourceServerStore(this.entityManager);
+ }
+
+ @Override
+ public ResourceStore getResourceStore() {
+ return new JPAResourceStore(this.entityManager);
+ }
+
+ @Override
+ public ScopeStore getScopeStore() {
+ return new JPAScopeStore(this.entityManager);
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index e504f2bcc76..5ceab0bbaad 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -39,11 +39,9 @@ import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
import java.util.Set;
/**
@@ -127,7 +125,7 @@ public class JpaRealmProvider implements RealmProvider {
return false;
}
em.refresh(realm);
- RealmAdapter adapter = new RealmAdapter(session, em, realm);
+ final RealmAdapter adapter = new RealmAdapter(session, em, realm);
session.users().preRemove(adapter);
realm.getDefaultGroups().clear();
@@ -159,6 +157,19 @@ public class JpaRealmProvider implements RealmProvider {
em.flush();
em.clear();
+
+ session.getKeycloakSessionFactory().publish(new RealmModel.RealmRemovedEvent() {
+ @Override
+ public RealmModel getRealm() {
+ return adapter;
+ }
+
+ @Override
+ public KeycloakSession getKeycloakSession() {
+ return session;
+ }
+ });
+
return true;
}
@@ -268,6 +279,19 @@ public class JpaRealmProvider implements RealmProvider {
int val = em.createNamedQuery("deleteGroupRoleMappingsByRole").setParameter("roleId", roleEntity.getId()).executeUpdate();
em.remove(roleEntity);
+
+ session.getKeycloakSessionFactory().publish(new RoleContainerModel.RoleRemovedEvent() {
+ @Override
+ public RoleModel getRole() {
+ return role;
+ }
+
+ @Override
+ public KeycloakSession getKeycloakSession() {
+ return session;
+ }
+ });
+
em.flush();
return true;
@@ -451,7 +475,7 @@ public class JpaRealmProvider implements RealmProvider {
@Override
public boolean removeClient(String id, RealmModel realm) {
- ClientModel client = getClientById(id, realm);
+ final ClientModel client = getClientById(id, realm);
if (client == null) return false;
session.users().preRemove(realm, client);
@@ -460,17 +484,32 @@ public class JpaRealmProvider implements RealmProvider {
client.removeRole(role);
}
-
ClientEntity clientEntity = ((ClientAdapter)client).getEntity();
+
em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
em.flush();
+
+ session.getKeycloakSessionFactory().publish(new RealmModel.ClientRemovedEvent() {
+ @Override
+ public ClientModel getClient() {
+ return client;
+ }
+
+ @Override
+ public KeycloakSession getKeycloakSession() {
+ return session;
+ }
+ });
+
em.remove(clientEntity); // i have no idea why, but this needs to come before deleteScopeMapping
+
try {
em.flush();
} catch (RuntimeException e) {
logger.errorv("Unable to delete client entity: {0} from realm {1}", client.getClientId(), realm.getName());
throw e;
}
+
return true;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 040d6eb38e4..593bae5dda9 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -25,6 +25,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
+import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
@@ -115,6 +116,17 @@ public class JpaUserProvider implements UserProvider {
UserEntity userEntity = em.find(UserEntity.class, user.getId());
if (userEntity == null) return false;
removeUser(userEntity);
+ session.getKeycloakSessionFactory().publish(new UserModel.UserRemovedEvent() {
+ @Override
+ public UserModel getUser() {
+ return user;
+ }
+
+ @Override
+ public KeycloakSession getKeycloakSession() {
+ return session;
+ }
+ });
return true;
}
@@ -134,6 +146,7 @@ public class JpaUserProvider implements UserProvider {
if (user != null) {
em.remove(user);
}
+
em.flush();
}
diff --git a/model/jpa/src/main/resources/META-INF/jpa-authz-changelog-2.0.0.xml b/model/jpa/src/main/resources/META-INF/jpa-authz-changelog-2.0.0.xml
new file mode 100755
index 00000000000..6e5e30c3d34
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-authz-changelog-2.0.0.xml
@@ -0,0 +1,168 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.0.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.0.0.xml
new file mode 100755
index 00000000000..72af496995a
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.0.0.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
index fd1f0f6e72b..131ce056724 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -32,4 +32,5 @@
+
diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml
index 7b6ee3760c6..b90c0fc232b 100755
--- a/model/jpa/src/main/resources/META-INF/persistence.xml
+++ b/model/jpa/src/main/resources/META-INF/persistence.xml
@@ -58,6 +58,12 @@
org.keycloak.events.jpa.EventEntity
org.keycloak.events.jpa.AdminEventEntity
+
+
+ org.keycloak.authorization.jpa.entities.ResourceServerEntity
+ org.keycloak.authorization.jpa.entities.ResourceEntity
+ org.keycloak.authorization.jpa.entities.ScopeEntity
+ org.keycloak.authorization.jpa.entities.PolicyEntity
true
diff --git a/model/jpa/src/main/resources/META-INF/services/org.keycloak.authorization.store.AuthorizationStoreFactory b/model/jpa/src/main/resources/META-INF/services/org.keycloak.authorization.store.AuthorizationStoreFactory
new file mode 100644
index 00000000000..46463ee5c88
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/services/org.keycloak.authorization.store.AuthorizationStoreFactory
@@ -0,0 +1,19 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2016 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed 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.
+#
+
+org.keycloak.authorization.jpa.store.JPAAuthorizationStoreFactory
\ No newline at end of file
diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml
index db244732eed..90e2afaedb3 100755
--- a/model/mongo/pom.xml
+++ b/model/mongo/pom.xml
@@ -31,6 +31,11 @@
Keycloak Model Mongo
+
+ 1.8
+ 1.8
+
+
org.bouncycastle
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/PolicyAdapter.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/PolicyAdapter.java
new file mode 100644
index 00000000000..a993c94cf82
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/PolicyAdapter.java
@@ -0,0 +1,168 @@
+package org.keycloak.authorization.mongo.adapter;
+
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
+import org.keycloak.authorization.mongo.entities.PolicyEntity;
+import org.keycloak.authorization.mongo.entities.ResourceEntity;
+import org.keycloak.authorization.mongo.entities.ScopeEntity;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.mongo.keycloak.adapters.AbstractMongoAdapter;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static javafx.scene.input.KeyCode.R;
+
+/**
+ * @author Pedro Igor
+ */
+public class PolicyAdapter extends AbstractMongoAdapter implements Policy {
+
+ private final PolicyEntity entity;
+ private final AuthorizationProvider authorizationProvider;
+
+ public PolicyAdapter(PolicyEntity entity, MongoStoreInvocationContext invocationContext, AuthorizationProvider authorizationProvider) {
+ super(invocationContext);
+ this.entity = entity;
+ this.authorizationProvider = authorizationProvider;
+ }
+
+ @Override
+ protected PolicyEntity getMongoEntity() {
+ return entity;
+ }
+
+ @Override
+ public String getId() {
+ return getMongoEntity().getId();
+ }
+
+ @Override
+ public String getType() {
+ return getMongoEntity().getType();
+ }
+
+ @Override
+ public DecisionStrategy getDecisionStrategy() {
+ return getMongoEntity().getDecisionStrategy();
+ }
+
+ @Override
+ public void setDecisionStrategy(DecisionStrategy decisionStrategy) {
+ getMongoEntity().setDecisionStrategy(decisionStrategy);
+ updateMongoEntity();
+ }
+
+ @Override
+ public Logic getLogic() {
+ return getMongoEntity().getLogic();
+ }
+
+ @Override
+ public void setLogic(Logic logic) {
+ getMongoEntity().setLogic(logic);
+ updateMongoEntity();
+ }
+
+ @Override
+ public Map getConfig() {
+ return getMongoEntity().getConfig();
+ }
+
+ @Override
+ public void setConfig(Map config) {
+ getMongoEntity().setConfig(config);
+ updateMongoEntity();
+ }
+
+ @Override
+ public String getName() {
+ return getMongoEntity().getName();
+ }
+
+ @Override
+ public void setName(String name) {
+ getMongoEntity().setName(name);
+ updateMongoEntity();
+ }
+
+ @Override
+ public String getDescription() {
+ return getMongoEntity().getDescription();
+ }
+
+ @Override
+ public void setDescription(String description) {
+ getMongoEntity().setDescription(description);
+ updateMongoEntity();
+ }
+
+ @Override
+ public ResourceServer getResourceServer() {
+ return this.authorizationProvider.getStoreFactory().getResourceServerStore().findById(getMongoEntity().getResourceServerId());
+ }
+
+ @Override
+ public Set getAssociatedPolicies() {
+ return getMongoEntity().getAssociatedPolicies().stream()
+ .map((Function) id -> authorizationProvider.getStoreFactory().getPolicyStore().findById(id))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set getResources() {
+ return getMongoEntity().getResources().stream()
+ .map((Function) id -> authorizationProvider.getStoreFactory().getResourceStore().findById(id))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set getScopes() {
+ return getMongoEntity().getScopes().stream()
+ .map((Function) id -> authorizationProvider.getStoreFactory().getScopeStore().findById(id))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public void addScope(Scope scope) {
+ getMongoEntity().addScope(scope.getId());
+ updateMongoEntity();
+ }
+
+ @Override
+ public void removeScope(Scope scope) {
+ getMongoEntity().removeScope(scope.getId());
+ updateMongoEntity();
+ }
+
+ @Override
+ public void addAssociatedPolicy(Policy associatedPolicy) {
+ getMongoEntity().addAssociatedPolicy(associatedPolicy.getId());
+ updateMongoEntity();
+ }
+
+ @Override
+ public void removeAssociatedPolicy(Policy associatedPolicy) {
+ getMongoEntity().removeAssociatedPolicy(associatedPolicy.getId());
+ updateMongoEntity();
+ }
+
+ @Override
+ public void addResource(Resource resource) {
+ getMongoEntity().addResource(resource.getId());
+ updateMongoEntity();
+ }
+
+ @Override
+ public void removeResource(Resource resource) {
+ getMongoEntity().removeResource(resource.getId());
+ updateMongoEntity();
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/ResourceAdapter.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/ResourceAdapter.java
new file mode 100644
index 00000000000..7c67f6ea75c
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/ResourceAdapter.java
@@ -0,0 +1,106 @@
+package org.keycloak.authorization.mongo.adapter;
+
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
+import org.keycloak.authorization.mongo.entities.ResourceEntity;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.mongo.keycloak.adapters.AbstractMongoAdapter;
+
+import java.util.List;
+import java.util.Set;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * @author Pedro Igor
+ */
+public class ResourceAdapter extends AbstractMongoAdapter implements Resource {
+
+ private final ResourceEntity entity;
+ private final AuthorizationProvider authorizationProvider;
+
+ public ResourceAdapter(ResourceEntity entity, MongoStoreInvocationContext invocationContext, AuthorizationProvider authorizationProvider) {
+ super(invocationContext);
+ this.entity = entity;
+ this.authorizationProvider = authorizationProvider;
+ }
+
+ @Override
+ public String getId() {
+ return getMongoEntity().getId();
+ }
+
+ @Override
+ public String getName() {
+ return getMongoEntity().getName();
+ }
+
+ @Override
+ public void setName(String name) {
+ getMongoEntity().setName(name);
+ updateMongoEntity();
+ }
+
+ @Override
+ public String getUri() {
+ return getMongoEntity().getUri();
+ }
+
+ @Override
+ public void setUri(String uri) {
+ getMongoEntity().setUri(uri);
+ updateMongoEntity();
+ }
+
+ @Override
+ public String getType() {
+ return getMongoEntity().getType();
+ }
+
+ @Override
+ public void setType(String type) {
+ getMongoEntity().setType(type);
+ updateMongoEntity();
+ }
+
+ @Override
+ public List getScopes() {
+ return getMongoEntity().getScopes().stream()
+ .map(id -> authorizationProvider.getStoreFactory().getScopeStore().findById(id))
+ .collect(toList());
+ }
+
+ @Override
+ public String getIconUri() {
+ return getMongoEntity().getIconUri();
+ }
+
+ @Override
+ public void setIconUri(String iconUri) {
+ getMongoEntity().setIconUri(iconUri);
+ updateMongoEntity();
+ }
+
+ @Override
+ public ResourceServer getResourceServer() {
+ return this.authorizationProvider.getStoreFactory().getResourceServerStore().findById(getMongoEntity().getResourceServerId());
+ }
+
+ @Override
+ public String getOwner() {
+ return getMongoEntity().getOwner();
+ }
+
+ @Override
+ public void updateScopes(Set scopes) {
+ getMongoEntity().updateScopes(scopes);
+ updateMongoEntity();
+ }
+
+ @Override
+ protected ResourceEntity getMongoEntity() {
+ return this.entity;
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/ResourceServerAdapter.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/ResourceServerAdapter.java
new file mode 100644
index 00000000000..72feedb82a2
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/ResourceServerAdapter.java
@@ -0,0 +1,56 @@
+package org.keycloak.authorization.mongo.adapter;
+
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.mongo.entities.ResourceServerEntity;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.mongo.keycloak.adapters.AbstractMongoAdapter;
+
+/**
+ * @author Pedro Igor
+ */
+public class ResourceServerAdapter extends AbstractMongoAdapter implements ResourceServer{
+
+ private final ResourceServerEntity entity;
+
+ public ResourceServerAdapter(ResourceServerEntity entity, MongoStoreInvocationContext invocationContext) {
+ super(invocationContext);
+ this.entity = entity;
+ }
+
+ @Override
+ public String getId() {
+ return getMongoEntity().getId();
+ }
+
+ @Override
+ public String getClientId() {
+ return getMongoEntity().getClientId();
+ }
+
+ @Override
+ public boolean isAllowRemoteResourceManagement() {
+ return getMongoEntity().isAllowRemoteResourceManagement();
+ }
+
+ @Override
+ public void setAllowRemoteResourceManagement(boolean allowRemoteResourceManagement) {
+ getMongoEntity().setAllowRemoteResourceManagement(allowRemoteResourceManagement);
+ updateMongoEntity();
+ }
+
+ @Override
+ public PolicyEnforcementMode getPolicyEnforcementMode() {
+ return getMongoEntity().getPolicyEnforcementMode();
+ }
+
+ @Override
+ public void setPolicyEnforcementMode(PolicyEnforcementMode enforcementMode) {
+ getMongoEntity().setPolicyEnforcementMode(enforcementMode);
+ updateMongoEntity();
+ }
+
+ @Override
+ protected ResourceServerEntity getMongoEntity() {
+ return this.entity;
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/ScopeAdapter.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/ScopeAdapter.java
new file mode 100644
index 00000000000..72196ca152d
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/ScopeAdapter.java
@@ -0,0 +1,60 @@
+package org.keycloak.authorization.mongo.adapter;
+
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
+import org.keycloak.authorization.mongo.entities.ScopeEntity;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.mongo.keycloak.adapters.AbstractMongoAdapter;
+
+/**
+ * @author Pedro Igor
+ */
+public class ScopeAdapter extends AbstractMongoAdapter implements Scope {
+
+ private final ScopeEntity entity;
+ private final AuthorizationProvider authorizationProvider;
+
+ public ScopeAdapter(ScopeEntity entity, MongoStoreInvocationContext invocationContext, AuthorizationProvider authorizationProvider) {
+ super(invocationContext);
+ this.entity = entity;
+ this.authorizationProvider = authorizationProvider;
+ }
+
+ @Override
+ public String getId() {
+ return getMongoEntity().getId();
+ }
+
+ @Override
+ public String getName() {
+ return getMongoEntity().getName();
+ }
+
+ @Override
+ public void setName(String name) {
+ getMongoEntity().setName(name);
+ updateMongoEntity();
+ }
+
+ @Override
+ public String getIconUri() {
+ return getMongoEntity().getIconUri();
+ }
+
+ @Override
+ public void setIconUri(String iconUri) {
+ getMongoEntity().setIconUri(iconUri);
+ updateMongoEntity();
+ }
+
+ @Override
+ public ResourceServer getResourceServer() {
+ return this.authorizationProvider.getStoreFactory().getResourceServerStore().findById(getMongoEntity().getResourceServerId());
+ }
+
+ @Override
+ protected ScopeEntity getMongoEntity() {
+ return this.entity;
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/entities/PolicyEntity.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/entities/PolicyEntity.java
new file mode 100644
index 00000000000..9230b88a14e
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/entities/PolicyEntity.java
@@ -0,0 +1,166 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.mongo.entities;
+
+import org.keycloak.authorization.model.Policy.DecisionStrategy;
+import org.keycloak.authorization.model.Policy.Logic;
+import org.keycloak.connections.mongo.api.MongoCollection;
+import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.entities.AbstractIdentifiableEntity;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Pedro Igor
+ */
+@MongoCollection(collectionName = "policies")
+public class PolicyEntity extends AbstractIdentifiableEntity implements MongoIdentifiableEntity {
+
+ private String name;
+
+ private String description;
+
+ private String type;
+
+ private DecisionStrategy decisionStrategy = DecisionStrategy.UNANIMOUS;
+
+ private Logic logic = Logic.POSITIVE;
+
+ private Map config = new HashMap();
+
+ private String resourceServerId;
+
+ private Set associatedPolicies = new HashSet<>();
+
+ private Set resources = new HashSet<>();
+
+ private Set scopes = new HashSet<>();
+
+ public String getType() {
+ return this.type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public DecisionStrategy getDecisionStrategy() {
+ return this.decisionStrategy;
+ }
+
+ public void setDecisionStrategy(DecisionStrategy decisionStrategy) {
+ this.decisionStrategy = decisionStrategy;
+ }
+
+ public Logic getLogic() {
+ return this.logic;
+ }
+
+ public void setLogic(Logic logic) {
+ this.logic = logic;
+ }
+
+ public Map getConfig() {
+ return this.config;
+ }
+
+ public void setConfig(Map config) {
+ this.config = config;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getResourceServerId() {
+ return this.resourceServerId;
+ }
+
+ public void setResourceServerId(String resourceServerId) {
+ this.resourceServerId = resourceServerId;
+ }
+
+ public Set getAssociatedPolicies() {
+ return this.associatedPolicies;
+ }
+
+ public void setAssociatedPolicies(Set associatedPolicies) {
+ this.associatedPolicies = associatedPolicies;
+ }
+
+ public Set getResources() {
+ return this.resources;
+ }
+
+ public void setResources(Set resources) {
+ this.resources = resources;
+ }
+
+ public Set getScopes() {
+ return this.scopes;
+ }
+
+ public void setScopes(Set scopes) {
+ this.scopes = scopes;
+ }
+
+ public void addScope(String scopeId) {
+ getScopes().add(scopeId);
+ }
+
+ public void removeScope(String scopeId) {
+ getScopes().remove(scopeId);
+ }
+
+ public void addAssociatedPolicy(String policyId) {
+ getAssociatedPolicies().add(policyId);
+ }
+
+ public void removeAssociatedPolicy(String policyId) {
+ getAssociatedPolicies().remove(policyId);
+ }
+
+ public void addResource(String resourceId) {
+ getResources().add(resourceId);
+ }
+
+ public void removeResource(String resourceId) {
+ getResources().remove(resourceId);
+ }
+
+ public void afterRemove(MongoStoreInvocationContext invocationContext) {
+
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/entities/ResourceEntity.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/entities/ResourceEntity.java
new file mode 100644
index 00000000000..b2e15da6300
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/entities/ResourceEntity.java
@@ -0,0 +1,142 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.mongo.entities;
+
+import org.keycloak.authorization.model.Scope;
+import org.keycloak.connections.mongo.api.MongoCollection;
+import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.entities.AbstractIdentifiableEntity;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Pedro Igor
+ */
+@MongoCollection(collectionName = "resources")
+public class ResourceEntity extends AbstractIdentifiableEntity implements MongoIdentifiableEntity {
+
+ private String name;
+
+ private String uri;
+
+ private String type;
+
+ private String iconUri;
+
+ private String owner;
+
+ private String resourceServerId;
+
+ private List scopes = new ArrayList<>();
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public List getScopes() {
+ return this.scopes;
+ }
+
+ public void setScopes(List scopes) {
+ this.scopes = scopes;
+ }
+
+ public String getIconUri() {
+ return iconUri;
+ }
+
+ public void setIconUri(String iconUri) {
+ this.iconUri = iconUri;
+ }
+
+ public String getResourceServerId() {
+ return resourceServerId;
+ }
+
+ public void setResourceServerId(String resourceServerId) {
+ this.resourceServerId = resourceServerId;
+ }
+
+ public String getOwner() {
+ return this.owner;
+ }
+
+ public void setOwner(String owner) {
+ this.owner = owner;
+ }
+
+ public void updateScopes(Set toUpdate) {
+ for (Scope scope : toUpdate) {
+ boolean hasScope = false;
+
+ for (String existingScope : this.scopes) {
+ if (existingScope.equals(scope.getId())) {
+ hasScope = true;
+ }
+ }
+
+ if (!hasScope) {
+ this.scopes.add(scope.getId());
+ }
+ }
+
+ for (String scopeId : new HashSet(this.scopes)) {
+ boolean hasScope = false;
+
+ for (Scope scope : toUpdate) {
+ if (scopeId.equals(scope.getId())) {
+ hasScope = true;
+ }
+ }
+
+ if (!hasScope) {
+ this.scopes.remove(scopeId);
+ }
+ }
+ }
+
+ @Override
+ public void afterRemove(MongoStoreInvocationContext invocationContext) {
+
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/entities/ResourceServerEntity.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/entities/ResourceServerEntity.java
new file mode 100644
index 00000000000..7013e1bfef5
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/entities/ResourceServerEntity.java
@@ -0,0 +1,67 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.mongo.entities;
+
+import org.keycloak.authorization.model.ResourceServer.PolicyEnforcementMode;
+import org.keycloak.connections.mongo.api.MongoCollection;
+import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.entities.AbstractIdentifiableEntity;
+
+/**
+ * @author Pedro Igor
+ */
+@MongoCollection(collectionName = "resource-servers")
+public class ResourceServerEntity extends AbstractIdentifiableEntity implements MongoIdentifiableEntity {
+
+ private String clientId;
+
+ private boolean allowRemoteResourceManagement;
+
+ private PolicyEnforcementMode policyEnforcementMode = PolicyEnforcementMode.ENFORCING;
+
+ public String getClientId() {
+ return this.clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public boolean isAllowRemoteResourceManagement() {
+ return this.allowRemoteResourceManagement;
+ }
+
+ public void setAllowRemoteResourceManagement(boolean allowRemoteResourceManagement) {
+ this.allowRemoteResourceManagement = allowRemoteResourceManagement;
+ }
+
+ public PolicyEnforcementMode getPolicyEnforcementMode() {
+ return this.policyEnforcementMode;
+ }
+
+ public void setPolicyEnforcementMode(PolicyEnforcementMode policyEnforcementMode) {
+ this.policyEnforcementMode = policyEnforcementMode;
+ }
+
+ @Override
+ public void afterRemove(MongoStoreInvocationContext invocationContext) {
+
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/entities/ScopeEntity.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/entities/ScopeEntity.java
new file mode 100644
index 00000000000..152127dcdd3
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/entities/ScopeEntity.java
@@ -0,0 +1,66 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.mongo.entities;
+
+import org.keycloak.connections.mongo.api.MongoCollection;
+import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.entities.AbstractIdentifiableEntity;
+
+/**
+ * @author Pedro Igor
+ */
+@MongoCollection(collectionName = "scopes")
+public class ScopeEntity extends AbstractIdentifiableEntity implements MongoIdentifiableEntity {
+
+ private String name;
+
+ private String iconUri;
+
+ private String resourceServerId;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getIconUri() {
+ return iconUri;
+ }
+
+ public void setIconUri(String iconUri) {
+ this.iconUri = iconUri;
+ }
+
+ public String getResourceServerId() {
+ return resourceServerId;
+ }
+
+ public void setResourceServerId(String resourceServerId) {
+ this.resourceServerId = resourceServerId;
+ }
+
+ @Override
+ public void afterRemove(MongoStoreInvocationContext invocationContext) {
+
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoAuthorizationStoreFactory.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoAuthorizationStoreFactory.java
new file mode 100644
index 00000000000..9a484ad49da
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoAuthorizationStoreFactory.java
@@ -0,0 +1,53 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.mongo.store;
+
+import org.keycloak.Config;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.store.AuthorizationStoreFactory;
+import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.connections.mongo.MongoConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+
+/**
+ * @author Pedro Igor
+ */
+public class MongoAuthorizationStoreFactory implements AuthorizationStoreFactory {
+ @Override
+ public StoreFactory create(KeycloakSession session) {
+ MongoConnectionProvider connection = session.getProvider(MongoConnectionProvider.class);
+ AuthorizationProvider provider = session.getProvider(AuthorizationProvider.class);
+ return new MongoStoreFactory(connection.getInvocationContext(), provider);
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return "mongo";
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java
new file mode 100644
index 00000000000..6f0ba5dcd70
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java
@@ -0,0 +1,171 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.mongo.store;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.mongo.adapter.PolicyAdapter;
+import org.keycloak.authorization.mongo.entities.PolicyEntity;
+import org.keycloak.authorization.store.PolicyStore;
+import org.keycloak.connections.mongo.api.MongoStore;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * @author Pedro Igor
+ */
+public class MongoPolicyStore implements PolicyStore {
+
+ private final MongoStoreInvocationContext invocationContext;
+ private final AuthorizationProvider authorizationProvider;
+
+ public MongoPolicyStore(MongoStoreInvocationContext invocationContext, AuthorizationProvider authorizationProvider) {
+ this.invocationContext = invocationContext;
+ this.authorizationProvider = authorizationProvider;
+ }
+
+ @Override
+ public Policy create(String name, String type, ResourceServer resourceServer) {
+ PolicyEntity entity = new PolicyEntity();
+
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setName(name);
+ entity.setType(type);
+ entity.setResourceServerId(resourceServer.getId());
+
+ getMongoStore().insertEntity(entity, getInvocationContext());
+
+ return new PolicyAdapter(entity, getInvocationContext(), this.authorizationProvider) ;
+ }
+
+ @Override
+ public void delete(String id) {
+ getMongoStore().removeEntity(PolicyEntity.class, id, getInvocationContext());
+ }
+
+ @Override
+ public Policy findById(String id) {
+ PolicyEntity entity = getMongoStore().loadEntity(PolicyEntity.class, id, getInvocationContext());
+
+ if (entity == null) {
+ return null;
+ }
+
+ return new PolicyAdapter(entity, getInvocationContext(), this.authorizationProvider);
+ }
+
+
+ @Override
+ public Policy findByName(String name, String resourceServerId) {
+ DBObject query = new QueryBuilder()
+ .and("resourceServerId").is(resourceServerId)
+ .and("name").is(name)
+ .get();
+
+ return getMongoStore().loadEntities(PolicyEntity.class, query, getInvocationContext()).stream()
+ .map(policyEntity -> findById(policyEntity.getId())).findFirst().orElse(null);
+ }
+
+ @Override
+ public List findByResourceServer(String resourceServerId) {
+ DBObject query = new QueryBuilder()
+ .and("resourceServerId").is(resourceServerId)
+ .get();
+
+ return getMongoStore().loadEntities(PolicyEntity.class, query, getInvocationContext()).stream()
+ .map(policyEntity -> findById(policyEntity.getId()))
+ .collect(toList());
+ }
+
+ @Override
+ public List findByResource(String resourceId) {
+ DBObject query = new QueryBuilder()
+ .and("resources").is(resourceId)
+ .get();
+
+ return getMongoStore().loadEntities(PolicyEntity.class, query, getInvocationContext()).stream()
+ .map(policyEntity -> findById(policyEntity.getId()))
+ .collect(toList());
+ }
+
+ @Override
+ public List findByResourceType(String resourceType, String resourceServerId) {
+ DBObject query = new QueryBuilder()
+ .and("resourceServerId").is(resourceServerId)
+ .get();
+
+ return getMongoStore().loadEntities(PolicyEntity.class, query, getInvocationContext()).stream()
+ .filter(policyEntity -> {
+ String defaultResourceType = policyEntity.getConfig().get("defaultResourceType");
+ return defaultResourceType != null && defaultResourceType.equals(resourceType);
+ })
+ .map(policyEntity -> findById(policyEntity.getId()))
+ .collect(toList());
+ }
+
+ @Override
+ public List findByScopeIds(List scopeIds, String resourceServerId) {
+ DBObject query = new QueryBuilder()
+ .and("resourceServerId").is(resourceServerId)
+ .and("scopes").in(scopeIds)
+ .get();
+
+ return getMongoStore().loadEntities(PolicyEntity.class, query, getInvocationContext()).stream()
+ .map(policyEntity -> findById(policyEntity.getId()))
+ .collect(toList());
+ }
+
+ @Override
+ public List findByType(String type) {
+ DBObject query = new QueryBuilder()
+ .and("type").is(type)
+ .get();
+
+ return getMongoStore().loadEntities(PolicyEntity.class, query, getInvocationContext()).stream()
+ .map(policyEntity -> findById(policyEntity.getId()))
+ .collect(toList());
+ }
+
+ @Override
+ public List findDependentPolicies(String policyId) {
+ DBObject query = new QueryBuilder()
+ .and("associatedPolicies").is(policyId)
+ .get();
+
+ return getMongoStore().loadEntities(PolicyEntity.class, query, getInvocationContext()).stream()
+ .map(policyEntity -> findById(policyEntity.getId()))
+ .collect(toList());
+ }
+
+ private MongoStoreInvocationContext getInvocationContext() {
+ return this.invocationContext;
+ }
+
+ private MongoStore getMongoStore() {
+ return getInvocationContext().getMongoStore();
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceServerStore.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceServerStore.java
new file mode 100644
index 00000000000..25e5f67301a
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceServerStore.java
@@ -0,0 +1,90 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.mongo.store;
+
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.mongo.adapter.ResourceServerAdapter;
+import org.keycloak.authorization.mongo.entities.ResourceServerEntity;
+import org.keycloak.authorization.store.ResourceServerStore;
+import org.keycloak.connections.mongo.api.MongoStore;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+/**
+ * @author Pedro Igor
+ */
+public class MongoResourceServerStore implements ResourceServerStore {
+
+ private final MongoStoreInvocationContext invocationContext;
+ private final AuthorizationProvider authorizationProvider;
+
+ public MongoResourceServerStore(MongoStoreInvocationContext invocationContext, AuthorizationProvider authorizationProvider) {
+ this.invocationContext = invocationContext;
+ this.authorizationProvider = authorizationProvider;
+ }
+
+ @Override
+ public ResourceServer create(String clientId) {
+ ResourceServerEntity entity = new ResourceServerEntity();
+
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setClientId(clientId);
+
+ getMongoStore().insertEntity(entity, getInvocationContext());
+
+ return new ResourceServerAdapter(entity, getInvocationContext());
+ }
+
+ @Override
+ public void delete(String id) {
+ getMongoStore().removeEntity(ResourceServerEntity.class, id, getInvocationContext());
+ }
+
+ @Override
+ public ResourceServer findById(String id) {
+ ResourceServerEntity entity = getMongoStore().loadEntity(ResourceServerEntity.class, id, getInvocationContext());
+
+ if (entity == null) {
+ return null;
+ }
+
+ return new ResourceServerAdapter(entity, getInvocationContext());
+ }
+
+ @Override
+ public ResourceServer findByClient(String clientId) {
+ DBObject query = new QueryBuilder()
+ .and("clientId").is(clientId)
+ .get();
+
+ return getMongoStore().loadEntities(ResourceServerEntity.class, query, getInvocationContext()).stream()
+ .map(resourceServer -> findById(resourceServer.getId())).findFirst().orElse(null);
+ }
+
+ private MongoStoreInvocationContext getInvocationContext() {
+ return this.invocationContext;
+ }
+
+ private MongoStore getMongoStore() {
+ return getInvocationContext().getMongoStore();
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java
new file mode 100644
index 00000000000..11b735ba6ce
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java
@@ -0,0 +1,141 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.mongo.store;
+
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.mongo.adapter.ResourceAdapter;
+import org.keycloak.authorization.mongo.entities.ResourceEntity;
+import org.keycloak.authorization.store.ResourceStore;
+import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.connections.mongo.api.MongoStore;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * @author Pedro Igor
+ */
+public class MongoResourceStore implements ResourceStore {
+
+ private final MongoStoreInvocationContext invocationContext;
+ private final AuthorizationProvider authorizationProvider;
+
+ public MongoResourceStore(MongoStoreInvocationContext invocationContext, AuthorizationProvider authorizationProvider) {
+ this.invocationContext = invocationContext;
+ this.authorizationProvider = authorizationProvider;
+ }
+
+ @Override
+ public Resource create(String name, ResourceServer resourceServer, String owner) {
+ ResourceEntity entity = new ResourceEntity();
+
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setName(name);
+ entity.setResourceServerId(resourceServer.getId());
+ entity.setOwner(owner);
+
+ getMongoStore().insertEntity(entity, getInvocationContext());
+
+ return new ResourceAdapter(entity, getInvocationContext(), this.authorizationProvider);
+ }
+
+ @Override
+ public void delete(String id) {
+ getMongoStore().removeEntity(ResourceEntity.class, id, getInvocationContext());
+ }
+
+ @Override
+ public Resource findById(String id) {
+ ResourceEntity entity = getMongoStore().loadEntity(ResourceEntity.class, id, getInvocationContext());
+
+ if (entity == null) {
+ return null;
+ }
+
+ return new ResourceAdapter(entity, getInvocationContext(), this.authorizationProvider);
+ }
+
+ @Override
+ public List findByOwner(String ownerId) {
+ DBObject query = new QueryBuilder()
+ .and("owner").is(ownerId)
+ .get();
+
+ return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream()
+ .map(scope -> findById(scope.getId())).collect(toList());
+ }
+
+ @Override
+ public List findByResourceServer(String resourceServerId) {
+ DBObject query = new QueryBuilder()
+ .and("resourceServerId").is(resourceServerId)
+ .get();
+
+ return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream()
+ .map(scope -> findById(scope.getId())).collect(toList());
+ }
+
+ @Override
+ public List findByScope(String... id) {
+ DBObject query = new QueryBuilder()
+ .and("scopes.id").in(id)
+ .get();
+
+ return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream()
+ .map(policyEntity -> findById(policyEntity.getId()))
+ .collect(toList());
+ }
+
+ @Override
+ public Resource findByName(String name, String resourceServerId) {
+ DBObject query = new QueryBuilder()
+ .and("name").is(name)
+ .and("resourceServerId").is(resourceServerId)
+ .get();
+
+ return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream()
+ .map(policyEntity -> findById(policyEntity.getId())).findFirst().orElse(null);
+ }
+
+ @Override
+ public List findByType(String type) {
+ DBObject query = new QueryBuilder()
+ .and("type").is(type)
+ .get();
+
+ return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream()
+ .map(policyEntity -> findById(policyEntity.getId()))
+ .collect(toList());
+ }
+
+ private MongoStoreInvocationContext getInvocationContext() {
+ return this.invocationContext;
+ }
+
+ private MongoStore getMongoStore() {
+ return getInvocationContext().getMongoStore();
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java
new file mode 100644
index 00000000000..e57b69bf6ee
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java
@@ -0,0 +1,108 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.mongo.store;
+
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
+import org.keycloak.authorization.mongo.adapter.ScopeAdapter;
+import org.keycloak.authorization.mongo.entities.ScopeEntity;
+import org.keycloak.authorization.store.ScopeStore;
+import org.keycloak.connections.mongo.api.MongoStore;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * @author Pedro Igor
+ */
+public class MongoScopeStore implements ScopeStore {
+
+ private final MongoStoreInvocationContext invocationContext;
+ private final AuthorizationProvider authorizationProvider;
+
+ public MongoScopeStore(MongoStoreInvocationContext invocationContext, AuthorizationProvider authorizationProvider) {
+ this.invocationContext = invocationContext;
+ this.authorizationProvider = authorizationProvider;
+ }
+
+ @Override
+ public Scope create(final String name, final ResourceServer resourceServer) {
+ ScopeEntity entity = new ScopeEntity();
+
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setName(name);
+ entity.setResourceServerId(resourceServer.getId());
+
+ getMongoStore().insertEntity(entity, getInvocationContext());
+
+ return new ScopeAdapter(entity, getInvocationContext(), this.authorizationProvider);
+ }
+
+ @Override
+ public void delete(String id) {
+ getMongoStore().removeEntity(ScopeEntity.class, id, getInvocationContext());
+ }
+
+ @Override
+ public Scope findById(String id) {
+ ScopeEntity entity = getMongoStore().loadEntity(ScopeEntity.class, id, getInvocationContext());
+
+ if (entity == null) {
+ return null;
+ }
+
+ return new ScopeAdapter(entity, getInvocationContext(), this.authorizationProvider);
+ }
+
+ @Override
+ public Scope findByName(String name, String resourceServerId) {
+ DBObject query = new QueryBuilder()
+ .and("resourceServerId").is(resourceServerId)
+ .and("name").is(name)
+ .get();
+
+ return getMongoStore().loadEntities(ScopeEntity.class, query, getInvocationContext()).stream()
+ .map(scope -> findById(scope.getId())).findFirst().orElse(null);
+ }
+
+ @Override
+ public List findByResourceServer(String resourceServerId) {
+ DBObject query = new QueryBuilder()
+ .and("resourceServerId").is(resourceServerId)
+ .get();
+
+ return getMongoStore().loadEntities(ScopeEntity.class, query, getInvocationContext()).stream()
+ .map(policyEntity -> findById(policyEntity.getId()))
+ .collect(toList());
+ }
+
+ private MongoStoreInvocationContext getInvocationContext() {
+ return this.invocationContext;
+ }
+
+ private MongoStore getMongoStore() {
+ return getInvocationContext().getMongoStore();
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoStoreFactory.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoStoreFactory.java
new file mode 100644
index 00000000000..7a94ba56873
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoStoreFactory.java
@@ -0,0 +1,66 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.mongo.store;
+
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.store.PolicyStore;
+import org.keycloak.authorization.store.ResourceServerStore;
+import org.keycloak.authorization.store.ResourceStore;
+import org.keycloak.authorization.store.ScopeStore;
+import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+
+/**
+ * @author Pedro Igor
+ */
+public class MongoStoreFactory implements StoreFactory {
+
+ private final MongoStoreInvocationContext invocationContext;
+ private final AuthorizationProvider authorizationProvider;
+
+ public MongoStoreFactory(MongoStoreInvocationContext invocationContext, AuthorizationProvider authorizationProvider) {
+ this.invocationContext = invocationContext;
+ this.authorizationProvider = authorizationProvider;
+ }
+
+ @Override
+ public PolicyStore getPolicyStore() {
+ return new MongoPolicyStore(this.invocationContext, this.authorizationProvider);
+ }
+
+ @Override
+ public ResourceServerStore getResourceServerStore() {
+ return new MongoResourceServerStore(this.invocationContext, this.authorizationProvider);
+ }
+
+ @Override
+ public ResourceStore getResourceStore() {
+ return new MongoResourceStore(this.invocationContext, this.authorizationProvider);
+ }
+
+ @Override
+ public ScopeStore getScopeStore() {
+ return new MongoScopeStore(this.invocationContext, this.authorizationProvider);
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
index 3bc9c7a6a99..491ad716c0f 100755
--- a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
@@ -74,6 +74,10 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
"org.keycloak.models.entities.RequiredActionProviderEntity",
"org.keycloak.models.entities.PersistentUserSessionEntity",
"org.keycloak.models.entities.PersistentClientSessionEntity",
+ "org.keycloak.authorization.mongo.entities.PolicyEntity",
+ "org.keycloak.authorization.mongo.entities.ResourceEntity",
+ "org.keycloak.authorization.mongo.entities.ResourceServerEntity",
+ "org.keycloak.authorization.mongo.entities.ScopeEntity"
};
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
index cef8a5a139b..14e5b033a03 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
@@ -129,10 +129,26 @@ public class MongoRealmProvider implements RealmProvider {
@Override
public boolean removeRealm(String id) {
- RealmModel realm = getRealm(id);
+ final RealmModel realm = getRealm(id);
if (realm == null) return false;
session.users().preRemove(realm);
- return getMongoStore().removeEntity(MongoRealmEntity.class, id, invocationContext);
+ boolean removed = getMongoStore().removeEntity(MongoRealmEntity.class, id, invocationContext);
+
+ if (removed) {
+ session.getKeycloakSessionFactory().publish(new RealmModel.RealmRemovedEvent() {
+ @Override
+ public RealmModel getRealm() {
+ return realm;
+ }
+
+ @Override
+ public KeycloakSession getKeycloakSession() {
+ return session;
+ }
+ });
+ }
+
+ return removed;
}
protected MongoStore getMongoStore() {
@@ -408,12 +424,27 @@ public class MongoRealmProvider implements RealmProvider {
@Override
public boolean removeClient(String id, RealmModel realm) {
if (id == null) return false;
- ClientModel client = getClientById(id, realm);
+ final ClientModel client = getClientById(id, realm);
if (client == null) return false;
session.users().preRemove(realm, client);
+ boolean removed = getMongoStore().removeEntity(MongoClientEntity.class, id, invocationContext);
- return getMongoStore().removeEntity(MongoClientEntity.class, id, invocationContext);
+ if (removed) {
+ session.getKeycloakSessionFactory().publish(new RealmModel.ClientRemovedEvent() {
+ @Override
+ public ClientModel getClient() {
+ return client;
+ }
+
+ @Override
+ public KeycloakSession getKeycloakSession() {
+ return session;
+ }
+ });
+ }
+
+ return removed;
}
@Override
diff --git a/model/mongo/src/main/resources/META-INF/services/org.keycloak.authorization.store.AuthorizationStoreFactory b/model/mongo/src/main/resources/META-INF/services/org.keycloak.authorization.store.AuthorizationStoreFactory
new file mode 100644
index 00000000000..e1d801cc483
--- /dev/null
+++ b/model/mongo/src/main/resources/META-INF/services/org.keycloak.authorization.store.AuthorizationStoreFactory
@@ -0,0 +1,37 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2016 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed 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.
+#
+
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2016 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed 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.
+#
+
+org.keycloak.authorization.mongo.store.MongoAuthorizationStoreFactory
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 38efdcffa9d..082891b4de8 100755
--- a/pom.xml
+++ b/pom.xml
@@ -75,6 +75,9 @@
1.1.0.Final
2.0.5
+
+ 6.4.0.Final
+
2.0.0-M17
1.0.0-M23
@@ -176,6 +179,7 @@
wildfly
integration
adapters
+ authz
examples
testsuite
@@ -412,6 +416,15 @@
${google.zxing.version}
+
+
+ org.drools
+ drools-bom
+ pom
+ ${version.org.drools}
+ import
+
+
com.icegreen
@@ -976,6 +989,49 @@
keycloak-services
${project.version}
+
+
+
+ org.keycloak
+ keycloak-authz-client
+ ${project.version}
+
+
+ org.keycloak
+ keycloak-authz-policy-drools
+ ${project.version}
+
+
+ org.keycloak
+ keycloak-authz-policy-resource
+ ${project.version}
+
+
+ org.keycloak
+ keycloak-authz-policy-scope
+ ${project.version}
+
+
+ org.keycloak
+ keycloak-authz-policy-identity
+ ${project.version}
+
+
+ org.keycloak
+ keycloak-authz-policy-js
+ ${project.version}
+
+
+ org.keycloak
+ keycloak-authz-policy-time
+ ${project.version}
+
+
+ org.keycloak
+ keycloak-authz-policy-aggregate
+ ${project.version}
+
+
org.keycloak
keycloak-saml-as7-modules
diff --git a/server-spi/pom.xml b/server-spi/pom.xml
index 047a065cd3c..9b71e2d088a 100755
--- a/server-spi/pom.xml
+++ b/server-spi/pom.xml
@@ -30,6 +30,11 @@
Keycloak Server SPI
+
+ 1.8
+ 1.8
+
+
org.jboss.resteasy
diff --git a/server-spi/src/main/java/org/keycloak/authorization/AuthorizationProvider.java b/server-spi/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
new file mode 100644
index 00000000000..ff646bbfe54
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
@@ -0,0 +1,137 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization;
+
+import org.keycloak.authorization.permission.evaluator.Evaluators;
+import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
+import org.keycloak.authorization.policy.provider.PolicyProvider;
+import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
+import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
+
+/**
+ * The main contract here is the creation of {@link org.keycloak.authorization.permission.evaluator.PermissionEvaluator} instances. Usually
+ * an application has a single {@link AuthorizationProvider} instance and threads servicing client requests obtain {@link org.keycloak.authorization.core.permission.evaluator.PermissionEvaluator}
+ * from the {@link #evaluators()} method.
+ *
+ *
The internal state of a {@link AuthorizationProvider} is immutable. This internal state includes all of the metadata
+ * used during the evaluation of policies.
+ *
+ *
Once created, {@link org.keycloak.authorization.permission.evaluator.PermissionEvaluator} instances can be obtained from the {@link #evaluators()} method:
+ *
+ *
+ * List permissionsToEvaluate = getPermissions(); // the permissions to evaluate
+ * EvaluationContext evaluationContext = createEvaluationContext(); // the context with runtime environment information
+ * PermissionEvaluator evaluator = authorization.evaluators().from(permissionsToEvaluate, context);
+ *
+ * evaluator.evaluate(new Decision() {
+ *
+ * public void onDecision(Evaluation evaluation) {
+ * // do something on grant
+ * }
+ *
+ * });
+ *
+ *
+ * @author Pedro Igor
+ */
+public final class AuthorizationProvider implements Provider {
+
+ private final DefaultPolicyEvaluator policyEvaluator;
+ private final Executor scheduller;
+ private final StoreFactory storeFactory;
+ private final List policyProviderFactories;
+ private final KeycloakSession keycloakSession;
+
+ public AuthorizationProvider(KeycloakSession session, StoreFactory storeFactory, Executor scheduller) {
+ this.keycloakSession = session;
+ this.storeFactory = storeFactory;
+ this.scheduller = scheduller;
+ this.policyProviderFactories = configurePolicyProviderFactories(session);
+ this.policyEvaluator = new DefaultPolicyEvaluator(this, this.policyProviderFactories);
+ }
+
+ public AuthorizationProvider(KeycloakSession session, StoreFactory storeFactory) {
+ this(session, storeFactory, Runnable::run);
+ }
+
+ /**
+ * Returns a {@link Evaluators} instance from where {@link org.keycloak.authorization.policy.evaluation.PolicyEvaluator} instances
+ * can be obtained.
+ *
+ * @return a {@link Evaluators} instance
+ */
+ public Evaluators evaluators() {
+ return new Evaluators(this.policyProviderFactories, this.policyEvaluator, this.scheduller);
+ }
+
+ /**
+ * Returns a {@link StoreFactory}.
+ *
+ * @return the {@link StoreFactory}
+ */
+ public StoreFactory getStoreFactory() {
+ return this.storeFactory;
+ }
+
+ /**
+ * Returns the registered {@link PolicyProviderFactory}.
+ *
+ * @return a {@link List} containing all registered {@link PolicyProviderFactory}
+ */
+ public List getProviderFactories() {
+ return this.policyProviderFactories;
+ }
+
+ /**
+ * Returns a {@link PolicyProviderFactory} given a type.
+ *
+ * @param type the type of the policy provider
+ * @param the expected type of the provider
+ * @return a {@link PolicyProviderFactory} with the given type
+ */
+ public F getProviderFactory(String type) {
+ return (F) getProviderFactories().stream().filter(policyProviderFactory -> policyProviderFactory.getId().equals(type)).findFirst().orElse(null);
+ }
+
+ public KeycloakSession getKeycloakSession() {
+ return this.keycloakSession;
+ }
+
+ private List configurePolicyProviderFactories(KeycloakSession session) {
+ List providerFactories = session.getKeycloakSessionFactory().getProviderFactories(PolicyProvider.class);
+
+ if (providerFactories.isEmpty()) {
+ throw new RuntimeException("Could not find any policy provider.");
+ }
+
+ return providerFactories.stream().map(providerFactory -> (PolicyProviderFactory) providerFactory).collect(Collectors.toList());
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/AuthorizationProviderFactory.java b/server-spi/src/main/java/org/keycloak/authorization/AuthorizationProviderFactory.java
new file mode 100644
index 00000000000..ae4dcc2fe91
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/AuthorizationProviderFactory.java
@@ -0,0 +1,27 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author Pedro Igor
+ */
+public interface AuthorizationProviderFactory extends ProviderFactory {
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/AuthorizationSpi.java b/server-spi/src/main/java/org/keycloak/authorization/AuthorizationSpi.java
new file mode 100644
index 00000000000..65028b3c0c6
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/AuthorizationSpi.java
@@ -0,0 +1,48 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author Pedro Igor
+ */
+public class AuthorizationSpi implements Spi {
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "authorization";
+ }
+
+ @Override
+ public Class extends Provider> getProviderClass() {
+ return AuthorizationProvider.class;
+ }
+
+ @Override
+ public Class extends ProviderFactory> getProviderFactoryClass() {
+ return AuthorizationProviderFactory.class;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/Decision.java b/server-spi/src/main/java/org/keycloak/authorization/Decision.java
new file mode 100644
index 00000000000..6ebd0862812
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/Decision.java
@@ -0,0 +1,41 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization;
+
+import org.keycloak.authorization.policy.evaluation.Evaluation;
+
+/**
+ * @author Pedro Igor
+ */
+public interface Decision {
+
+ enum Effect {
+ PERMIT,
+ DENY
+ }
+
+ void onDecision(D evaluation);
+
+ default void onError(Throwable cause) {
+ throw new RuntimeException("Not implemented.", cause);
+ }
+
+ default void onComplete() {
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java b/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
new file mode 100644
index 00000000000..ce2bc51921a
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
@@ -0,0 +1,143 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.attribute;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+
+import static java.util.Collections.emptyList;
+
+/**
+ * Holds attributes, their values and provides utlity methods to manage them.
+ *
+ *
In the future, it may be useful to provide different implementations for this interface in order to plug or integrate with different
+ * Policy Information Point (PIP).
+ *
+ * @author Pedro Igor
+ */
+public interface Attributes {
+
+ static Attributes from(Map> attributes) {
+ return () -> attributes;
+ }
+
+ /**
+ * Converts to a {@link Map}.
+ *
+ * @return
+ */
+ Map> toMap();
+
+ /**
+ * Checks if there is an attribute with the given name.
+ *
+ * @param name the attribute name
+ * @return true if any attribute with name exist. Otherwise, returns false.
+ */
+ default boolean exists(String name) {
+ return toMap().containsKey(name);
+ }
+
+ /**
+ * Checks if there is an attribute with the given name and value.
+ *
+ * @param name the attribute name
+ * @param value the attribute value
+ * @return true if any attribute with name and value exist. Otherwise, returns false.
+ */
+ default boolean containsValue(String name, String value) {
+ return toMap().getOrDefault(name, emptyList()).stream().anyMatch(value::equals);
+ }
+
+ /**
+ * Returns a {@link Entry} from where values can be obtained and parsed accordingly.
+ *
+ * @param name the attribute name
+ * @return an {@link Entry} holding the values for an attribute
+ */
+ default Entry getValue(String name) {
+ Collection value = toMap().get(name);
+
+ if (value != null) {
+ return new Entry(name, value);
+ }
+
+ return null;
+ }
+
+ /**
+ * Holds an attribute and its values, providing useful methods for obtaining and formatting values. Specially useful
+ * for writing rule-based policies.
+ */
+ class Entry {
+
+ private final String[] values;
+ private final String name;
+
+ Entry(String name, Collection values) {
+ this.name = name;
+ this.values = values.toArray(new String[values.size()]);
+ }
+
+ private String getName() {
+ return this.name;
+ }
+
+ public int size() {
+ return values.length;
+ }
+
+ public String asString(int idx) {
+ if (idx >= values.length) {
+ throw new IllegalArgumentException("Invalid index [" + idx + "]. Values are [" + values + "].");
+ }
+
+ return values[idx];
+ }
+
+ public int asInt(int idx) {
+ return Integer.parseInt(asString(idx));
+ }
+
+ public Date asDate(int idx, String pattern) {
+ try {
+ return new SimpleDateFormat(pattern).parse(asString(idx));
+ } catch (ParseException e) {
+ throw new RuntimeException("Error parsing date.", e);
+ }
+ }
+
+ public InetAddress asInetAddress(int idx) {
+ try {
+ return InetAddress.getByName(asString(idx));
+ } catch (UnknownHostException e) {
+ throw new RuntimeException("Error parsing address.", e);
+ }
+ }
+
+ public long asLong(int idx) {
+ return Long.parseLong(asString(idx));
+ }
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/attribute/package-info.java b/server-spi/src/main/java/org/keycloak/authorization/attribute/package-info.java
new file mode 100644
index 00000000000..c75b520a2a1
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/attribute/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Provides classes related with the representation of attributes and their manipulation.
+ *
+ * @author Pedro Igor
+ */
+package org.keycloak.authorization.attribute;
\ No newline at end of file
diff --git a/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java b/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java
new file mode 100644
index 00000000000..ebc9679994c
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java
@@ -0,0 +1,57 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.identity;
+
+import org.keycloak.authorization.attribute.Attributes;
+
+/**
+ * Represents a security identity, which can be a person or non-person entity that was previously authenticated.
+ *
+ *
An {@link Identity} plays an important role during the evaluation of policies as they represent the entity to which one or more permissions
+ * should be granted or not, providing additional information and attributes that can be relevant to the different
+ * access control methods involved during the evaluation of policies.
+ *
+ * @author Pedro Igor
+ */
+public interface Identity {
+
+ /**
+ * Returns the unique identifier of this identity.
+ *
+ * @return the unique identifier of this identity
+ */
+ String getId();
+
+ /**
+ * Returns the attributes or claims associated with this identity.
+ *
+ * @return the attributes or claims associated with this identity
+ */
+ Attributes getAttributes();
+
+ /**
+ * Indicates if this identity is granted with a role with the given roleName.
+ *
+ * @param roleName the name of the role
+ *
+ * @return true if the identity has the given role. Otherwise, it returns false.
+ */
+ default boolean hasRole(String roleName) {
+ return getAttributes().containsValue("roles", roleName);
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/identity/package-info.java b/server-spi/src/main/java/org/keycloak/authorization/identity/package-info.java
new file mode 100644
index 00000000000..47a5746dfee
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/identity/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Provides classes related with the representation and management of identities.
+ *
+ * @author Pedro Igor
+ */
+package org.keycloak.authorization.identity;
\ No newline at end of file
diff --git a/server-spi/src/main/java/org/keycloak/authorization/model/Policy.java b/server-spi/src/main/java/org/keycloak/authorization/model/Policy.java
new file mode 100644
index 00000000000..1960d6abb44
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/model/Policy.java
@@ -0,0 +1,193 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.model;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents an authorization policy and all the configuration associated with it.
+ *
+ * @author Pedro Igor
+ */
+public interface Policy {
+
+ /**
+ * Returns the unique identifier for this instance.
+ *
+ * @return the unique identifier for this instance
+ */
+ String getId();
+
+ /**
+ * Returns the type of this policy.
+ *
+ * @return the type of this policy
+ */
+ String getType();
+
+ /**
+ * Returns the {@link DecisionStrategy} for this policy.
+ *
+ * @return the decision strategy defined for this policy
+ */
+ DecisionStrategy getDecisionStrategy();
+
+ /**
+ * Sets the {DecisionStrategy} for this policy.
+ *
+ * @return the decision strategy for this policy
+ */
+ void setDecisionStrategy(DecisionStrategy decisionStrategy);
+
+ /**
+ * Returns the {@link Logic} for this policy.
+ *
+ * @return the decision strategy defined for this policy
+ */
+ Logic getLogic();
+
+ /**
+ * Sets the {Logic} for this policy.
+ *
+ * @return the decision strategy for this policy
+ */
+ void setLogic(Logic logic);
+
+ /**
+ * Returns a {@link Map} holding string-based key/value pairs representing any additional configuration for this policy.
+ *
+ * @return a map with any additional configuration defined for this policy.
+ */
+ Map getConfig();
+
+ /**
+ * Sets a {@link Map} with string-based key/value pairs representing any additional configuration for this policy.
+ *
+ * @return a map with any additional configuration for this policy.
+ */
+ void setConfig(Map config);
+
+ /**
+ * Returns the name of this policy.
+ *
+ * @return the name of this policy
+ */
+ String getName();
+
+ /**
+ * Sets an unique name to this policy.
+ *
+ * @param name an unique name
+ */
+ void setName(String name);
+
+ /**
+ * Returns the description of this policy.
+ *
+ * @return a description or null of there is no description
+ */
+ String getDescription();
+
+ /**
+ * Sets the description for this policy.
+ *
+ * @param description a description
+ */
+ void setDescription(String description);
+
+ /**
+ * Returns the {@link ResourceServer} where this policy belongs to.
+ *
+ * @return a resource server
+ */
+ R getResourceServer();
+
+ /**
+ * Returns the {@link Policy} instances associated with this policy and used to evaluate authorization decisions when
+ * this policy applies.
+ *
+ * @return the associated policies or an empty set if no policy is associated with this policy
+ */
+ Set
getAssociatedPolicies();
+
+ /**
+ * Returns the {@link Resource} instances where this policy applies.
+ *
+ * @return a set with all resource instances where this policy applies. Or an empty set if there is no resource associated with this policy
+ */
+ Set getResources();
+
+ /**
+ * Returns the {@link Scope} instances where this policy applies.
+ *
+ * @return a set with all scope instances where this policy applies. Or an empty set if there is no scope associated with this policy
+ */
+ Set getScopes();
+
+ void addScope(Scope scope);
+
+ void removeScope(Scope scope);
+
+ void addAssociatedPolicy(Policy associatedPolicy);
+
+ void removeAssociatedPolicy(Policy associatedPolicy);
+
+ void addResource(Resource resource);
+
+ void removeResource(Resource resource);
+
+ /**
+ * The decision strategy dictates how the policies associated with a given policy are evaluated and how a final decision
+ * is obtained.
+ */
+ enum DecisionStrategy {
+ /**
+ * Defines that at least one policy must evaluate to a positive decision in order to the overall decision be also positive.
+ */
+ AFFIRMATIVE,
+
+ /**
+ * Defines that all policies must evaluate to a positive decision in order to the overall decision be also positive.
+ */
+ UNANIMOUS,
+
+ /**
+ * Defines that the number of positive decisions must be greater than the number of negative decisions. If the number of positive and negative is the same,
+ * the final decision will be negative.
+ */
+ CONSENSUS
+ }
+
+ /**
+ * The decision strategy dictates how the policies associated with a given policy are evaluated and how a final decision
+ * is obtained.
+ */
+ enum Logic {
+ /**
+ * Defines that this policy follows a positive logic. In other words, the final decision is the policy outcome.
+ */
+ POSITIVE,
+
+ /**
+ * Defines that this policy uses a logical negation. In other words, the final decision would be a negative of the policy outcome.
+ */
+ NEGATIVE,
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/model/Resource.java b/server-spi/src/main/java/org/keycloak/authorization/model/Resource.java
new file mode 100644
index 00000000000..2bf2c6fa3f3
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/model/Resource.java
@@ -0,0 +1,116 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.model;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Represents a resource, which is usually protected by a set of policies within a resource server.
+ *
+ * @author Pedro Igor
+ */
+public interface Resource {
+
+ /**
+ * Returns the unique identifier for this instance.
+ *
+ * @return the unique identifier for this instance
+ */
+ String getId();
+
+ /**
+ * Returns the resource's name.
+ *
+ * @return the name of this resource
+ */
+ String getName();
+
+ /**
+ * Sets a name for this resource. The name must be unique.
+ *
+ * @param name the name of this resource
+ */
+ void setName(String name);
+
+ /**
+ * Returns a {@link java.net.URI} that uniquely identify this resource.
+ *
+ * @return an {@link java.net.URI} for this resource or null if not defined.
+ */
+ String getUri();
+
+ /**
+ * Sets a {@link java.net.URI} that uniquely identify this resource.
+ *
+ * @param uri an {@link java.net.URI} for this resource
+ */
+ void setUri(String uri);
+
+ /**
+ * Returns a string representing the type of this resource.
+ *
+ * @return the type of this resource or null if not defined
+ */
+ String getType();
+
+ /**
+ * Sets a string representing the type of this resource.
+ *
+ * @return the type of this resource or null if not defined
+ */
+ void setType(String type);
+
+ /**
+ * Returns a {@link List} containing all the {@link Scope} associated with this resource.
+ *
+ * @return a list with all scopes associated with this resource
+ */
+ List getScopes();
+
+ /**
+ * Returns an icon {@link java.net.URI} for this resource.
+ *
+ * @return a uri for an icon
+ */
+ String getIconUri();
+
+ /**
+ * Sets an icon {@link java.net.URI} for this resource.
+ *
+ * @return a uri for an icon
+ */
+ void setIconUri(String iconUri);
+
+ /**
+ * Returns the {@link ResourceServer} to where this resource belongs to.
+ *
+ * @return the resource server associated with this resource
+ */
+ R getResourceServer();
+
+ /**
+ * Returns the resource's owner, which is usually an identifier that uniquely identifies the resource's owner.
+ *
+ * @return the owner of this resource
+ */
+ String getOwner();
+
+ void updateScopes(Set scopes);
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/model/ResourceServer.java b/server-spi/src/main/java/org/keycloak/authorization/model/ResourceServer.java
new file mode 100644
index 00000000000..2424c8d22e7
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/model/ResourceServer.java
@@ -0,0 +1,91 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.model;
+
+/**
+ * Represents a resource server, whose resources are managed and protected. A resource server is basically an existing
+ * client application in Keycloak that will also act as a resource server.
+ *
+ * @author Pedro Igor
+ */
+public interface ResourceServer {
+
+ /**
+ * Returns the unique identifier for this instance.
+ *
+ * @return the unique identifier for this instance
+ */
+ String getId();
+
+ /**
+ * Returns the identifier of the client application (which already exists in Keycloak) that is also acting as a resource
+ * server.
+ *
+ * @return the identifier of the client application associated with this instance.
+ */
+ String getClientId();
+
+ /**
+ * Indicates if the resource server is allowed to manage its own resources remotely using the Protection API.
+ *
+ * {@code true} if the resource server is allowed to managed them remotely
+ */
+ boolean isAllowRemoteResourceManagement();
+
+ /**
+ * Indicates if the resource server is allowed to manage its own resources remotely using the Protection API.
+ *
+ * @param allowRemoteResourceManagement {@code true} if the resource server is allowed to managed them remotely
+ */
+ void setAllowRemoteResourceManagement(boolean allowRemoteResourceManagement);
+
+ /**
+ * Returns the {@code PolicyEnforcementMode} configured for this instance.
+ *
+ * @return the {@code PolicyEnforcementMode} configured for this instance.
+ */
+ PolicyEnforcementMode getPolicyEnforcementMode();
+
+ /**
+ * Defines a {@code PolicyEnforcementMode} for this instance.
+ *
+ * @param enforcementMode one of the available options in {@code PolicyEnforcementMode}
+ */
+ void setPolicyEnforcementMode(PolicyEnforcementMode enforcementMode);
+
+ /**
+ * The policy enforcement mode dictates how authorization requests are handled by the server.
+ */
+ enum PolicyEnforcementMode {
+ /**
+ * Requests are denied by default even when there is no policy associated with a given resource.
+ */
+ ENFORCING,
+
+ /**
+ * Requests are allowed even when there is no policy associated with a given resource.
+ */
+ PERMISSIVE,
+
+ /**
+ * Completely disables the evaluation of policies and allow access to any resource.
+ */
+ DISABLED
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/model/Scope.java b/server-spi/src/main/java/org/keycloak/authorization/model/Scope.java
new file mode 100644
index 00000000000..e13a789d8ae
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/model/Scope.java
@@ -0,0 +1,70 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.model;
+
+/**
+ * Represents a scope, which is usually associated with one or more resources in order to define the actions that can be performed
+ * or a specific access context.
+ *
+ * @author Pedro Igor
+ */
+public interface Scope {
+
+ /**
+ * Returns the unique identifier for this instance.
+ *
+ * @return the unique identifier for this instance
+ */
+ String getId();
+
+ /**
+ * Returns the name of this scope.
+ *
+ * @return the name of this scope
+ */
+ String getName();
+
+ /**
+ * Sets a name for this scope. The name must be unique.
+ *
+ * @param name the name of this scope
+ */
+ void setName(String name);
+
+ /**
+ * Returns an icon {@link java.net.URI} for this scope.
+ *
+ * @return a uri for an icon
+ */
+ String getIconUri();
+
+ /**
+ * Sets an icon {@link java.net.URI} for this scope.
+ *
+ * @return a uri for an icon
+ */
+ void setIconUri(String iconUri);
+
+ /**
+ * Returns the {@link ResourceServer} instance to where this scope belongs to.
+ *
+ * @return
+ */
+ ResourceServer getResourceServer();
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/model/package-info.java b/server-spi/src/main/java/org/keycloak/authorization/model/package-info.java
new file mode 100644
index 00000000000..38f9a8fdc88
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/model/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Provides the domain model and any other type related with it
+ *
+ * @author Pedro Igor
+ */
+package org.keycloak.authorization.model;
\ No newline at end of file
diff --git a/server-spi/src/main/java/org/keycloak/authorization/package-info.java b/server-spi/src/main/java/org/keycloak/authorization/package-info.java
new file mode 100644
index 00000000000..6ff51afbcb6
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Fine-grained Authorization SPI.
+ *
+ * @author Pedro Igor
+ */
+package org.keycloak.authorization;
\ No newline at end of file
diff --git a/server-spi/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java b/server-spi/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java
new file mode 100644
index 00000000000..1eef22acdc6
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java
@@ -0,0 +1,71 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.permission;
+
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a permission for a given resource.
+ *
+ * @author Pedro Igor
+ */
+public class ResourcePermission {
+
+ private final Resource resource;
+ private final List scopes;
+ private ResourceServer resourceServer;
+
+ public ResourcePermission(Resource resource, List scopes, ResourceServer resourceServer) {
+ this.resource = resource;
+ this.scopes = scopes;
+ this.resourceServer = resourceServer;
+ }
+
+ /**
+ * Returns the resource to which this permission applies.
+ *
+ * @return the resource to which this permission applies
+ */
+ public Resource getResource() {
+ return this.resource;
+ }
+
+ /**
+ * Returns a list of permitted scopes associated with the resource
+ *
+ * @return a lit of permitted scopes
+ */
+ public List getScopes() {
+ return Collections.unmodifiableList(this.scopes);
+ }
+
+ /**
+ * Returns the resource server associated with this permission.
+ *
+ * @return the resource server
+ */
+ public ResourceServer getResourceServer() {
+ return this.resourceServer;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java b/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java
new file mode 100644
index 00000000000..e26ad1c87ec
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java
@@ -0,0 +1,53 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.permission.evaluator;
+
+import org.keycloak.authorization.permission.ResourcePermission;
+import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
+import org.keycloak.authorization.policy.evaluation.EvaluationContext;
+import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * A factory for the different {@link PermissionEvaluator} implementations.
+ *
+ * @author Pedro Igor
+ */
+public final class Evaluators {
+
+ private final List policyProviderFactories;
+ private final DefaultPolicyEvaluator policyEvaluator;
+ private final Executor scheduler;
+
+ public Evaluators(List policyProviderFactories, DefaultPolicyEvaluator policyEvaluator, Executor scheduler) {
+ this.policyProviderFactories = policyProviderFactories;
+ this.policyEvaluator = policyEvaluator;
+ this.scheduler = scheduler;
+ }
+
+ public PermissionEvaluator from(List permissions, EvaluationContext evaluationContext) {
+ return schedule(permissions, evaluationContext);
+ }
+
+ public PermissionEvaluator schedule(List permissions, EvaluationContext evaluationContext) {
+ return new ScheduledPermissionEvaluator(new IterablePermissionEvaluator(permissions.iterator(), evaluationContext, this.policyEvaluator), this.scheduler);
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java b/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java
new file mode 100644
index 00000000000..dfda6a7bb6d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java
@@ -0,0 +1,53 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.permission.evaluator;
+
+import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.permission.ResourcePermission;
+import org.keycloak.authorization.policy.evaluation.EvaluationContext;
+import org.keycloak.authorization.policy.evaluation.PolicyEvaluator;
+
+import java.util.Iterator;
+
+/**
+ * @author Pedro Igor
+ */
+class IterablePermissionEvaluator implements PermissionEvaluator {
+
+ private final Iterator permissions;
+ private final EvaluationContext executionContext;
+ private final PolicyEvaluator policyEvaluator;
+
+ IterablePermissionEvaluator(Iterator permissions, EvaluationContext executionContext, PolicyEvaluator policyEvaluator) {
+ this.permissions = permissions;
+ this.executionContext = executionContext;
+ this.policyEvaluator = policyEvaluator;
+ }
+
+ @Override
+ public void evaluate(Decision decision) {
+ try {
+ while (this.permissions.hasNext()) {
+ this.policyEvaluator.evaluate(this.permissions.next(), this.executionContext, decision);
+ }
+ decision.onComplete();
+ } catch (Throwable cause) {
+ decision.onError(cause);
+ }
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java b/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java
new file mode 100644
index 00000000000..c129caf02dd
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java
@@ -0,0 +1,31 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.permission.evaluator;
+
+import org.keycloak.authorization.Decision;
+
+/**
+ * An {@link PermissionEvaluator} represents a source of {@link org.keycloak.authorization.permission.ResourcePermission}, responsible for emitting these permissions
+ * to a consumer in order to evaluate the authorization policies based on a {@link org.keycloak.authorization.policy.evaluation.EvaluationContext}.
+ *
+ * @author Pedro Igor
+ */
+public interface PermissionEvaluator {
+
+ void evaluate(Decision decision);
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java b/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java
new file mode 100644
index 00000000000..13e08e4ebc4
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.permission.evaluator;
+
+import org.keycloak.authorization.Decision;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+
+/**
+ * @author Pedro Igor
+ * @see PermissionEvaluator
+ */
+class ScheduledPermissionEvaluator implements PermissionEvaluator {
+
+ private final PermissionEvaluator publisher;
+ private final Executor scheduler;
+
+ ScheduledPermissionEvaluator(PermissionEvaluator publisher, Executor scheduler) {
+ this.publisher = publisher;
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public void evaluate(Decision decision) {
+ CompletableFuture.runAsync(() -> publisher.evaluate(decision), scheduler);
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
new file mode 100644
index 00000000000..f06eb3f4f26
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
@@ -0,0 +1,102 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.policy.evaluation;
+
+import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.permission.ResourcePermission;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Pedro Igor
+ */
+public abstract class DecisionResultCollector implements Decision {
+
+ private Map results = new HashMap();
+
+ @Override
+ public void onDecision(DefaultEvaluation evaluation) {
+ if (evaluation.getParentPolicy() != null) {
+ results.computeIfAbsent(evaluation.getPermission(), Result::new).policy(evaluation.getParentPolicy()).policy(evaluation.getPolicy()).setStatus(evaluation.getEffect());
+ } else {
+ results.computeIfAbsent(evaluation.getPermission(), Result::new).setStatus(evaluation.getEffect());
+ }
+ }
+
+ @Override
+ public void onComplete() {
+ for (Result result : results.values()) {
+ for (Result.PolicyResult policyResult : result.getResults()) {
+ if (isGranted(policyResult)) {
+ policyResult.setStatus(Effect.PERMIT);
+ } else {
+ policyResult.setStatus(Effect.DENY);
+ }
+ }
+
+ if (result.getResults().stream()
+ .filter(policyResult -> Effect.DENY.equals(policyResult.getStatus())).count() > 0) {
+ result.setStatus(Effect.DENY);
+ } else {
+ result.setStatus(Effect.PERMIT);
+ }
+ }
+
+ onComplete(results.values().stream().collect(Collectors.toList()));
+ }
+
+ protected abstract void onComplete(List results);
+
+ private boolean isGranted(Result.PolicyResult policyResult) {
+ List values = policyResult.getAssociatedPolicies();
+
+ int grantCount = 0;
+ int denyCount = policyResult.getPolicy().getAssociatedPolicies().size();
+
+ for (Result.PolicyResult decision : values) {
+ if (decision.getStatus().equals(Effect.PERMIT)) {
+ grantCount++;
+ denyCount--;
+ }
+ }
+
+ Policy policy = policyResult.getPolicy();
+ Policy.DecisionStrategy decisionStrategy = policy.getDecisionStrategy();
+
+ if (decisionStrategy == null) {
+ decisionStrategy = Policy.DecisionStrategy.UNANIMOUS;
+ }
+
+ if (Policy.DecisionStrategy.AFFIRMATIVE.equals(decisionStrategy) && grantCount > 0) {
+ return true;
+ } else if (Policy.DecisionStrategy.UNANIMOUS.equals(decisionStrategy) && denyCount == 0) {
+ return true;
+ } else if (Policy.DecisionStrategy.CONSENSUS.equals(decisionStrategy)) {
+ if (grantCount > denyCount) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java
new file mode 100644
index 00000000000..df379af30e2
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java
@@ -0,0 +1,105 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.policy.evaluation;
+
+import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.Decision.Effect;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Policy.Logic;
+import org.keycloak.authorization.permission.ResourcePermission;
+
+/**
+ * @author Pedro Igor
+ */
+public class DefaultEvaluation implements Evaluation {
+
+ private final ResourcePermission permission;
+ private final EvaluationContext executionContext;
+ private final Decision decision;
+ private final Policy policy;
+ private final Policy parentPolicy;
+ private Effect effect;
+
+ public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Policy policy, Decision decision) {
+ this.permission = permission;
+ this.executionContext = executionContext;
+ this.parentPolicy = parentPolicy;
+ this.policy = policy;
+ this.decision = decision;
+ }
+
+ /**
+ * Returns the {@link ResourcePermission} to be evaluated.
+ *
+ * @return the permission to be evaluated
+ */
+ public ResourcePermission getPermission() {
+ return this.permission;
+ }
+
+ /**
+ * Returns the {@link org.keycloak.authorization.permission.evaluator.PermissionEvaluator}. Which provides access to the whole evaluation runtime context.
+ *
+ * @return the evaluation context
+ */
+ public EvaluationContext getContext() {
+ return this.executionContext;
+ }
+
+ /**
+ * Grants all the requested permissions to the caller.
+ */
+ public void grant() {
+ if (policy != null && Logic.NEGATIVE.equals(policy.getLogic())) {
+ this.effect = Effect.DENY;
+ } else {
+ this.effect = Effect.PERMIT;
+ }
+
+ this.decision.onDecision(this);
+ }
+
+ public void deny() {
+ if (policy != null && Logic.NEGATIVE.equals(policy.getLogic())) {
+ this.effect = Effect.PERMIT;
+ } else {
+ this.effect = Effect.DENY;
+ }
+
+ this.decision.onDecision(this);
+ }
+
+ public Policy getPolicy() {
+ return this.policy;
+ }
+
+ public Policy getParentPolicy() {
+ return this.parentPolicy;
+ }
+
+ public Effect getEffect() {
+ return effect;
+ }
+
+ void denyIfNoEffect() {
+ if (this.effect == null) {
+ deny();
+ }
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
new file mode 100644
index 00000000000..8b12558a671
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
@@ -0,0 +1,156 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.policy.evaluation;
+
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.ResourceServer.PolicyEnforcementMode;
+import org.keycloak.authorization.model.Scope;
+import org.keycloak.authorization.permission.ResourcePermission;
+import org.keycloak.authorization.policy.provider.PolicyProvider;
+import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
+import org.keycloak.authorization.store.PolicyStore;
+import org.keycloak.authorization.store.StoreFactory;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+/**
+ * @author Pedro Igor
+ */
+public class DefaultPolicyEvaluator implements PolicyEvaluator {
+
+ private final AuthorizationProvider authorization;
+ private Map policyProviders = new HashMap<>();
+
+ public DefaultPolicyEvaluator(AuthorizationProvider authorization, List policyProviderFactories) {
+ this.authorization = authorization;
+
+ for (PolicyProviderFactory providerFactory : policyProviderFactories) {
+ this.policyProviders.put(providerFactory.getId(), providerFactory);
+ }
+ }
+
+ @Override
+ public void evaluate(ResourcePermission permission, EvaluationContext executionContext, Decision decision) {
+ ResourceServer resourceServer = permission.getResourceServer();
+
+ if (PolicyEnforcementMode.DISABLED.equals(resourceServer.getPolicyEnforcementMode())) {
+ createEvaluation(permission, executionContext, decision, null, null).grant();
+ return;
+ }
+
+ StoreFactory storeFactory = this.authorization.getStoreFactory();
+ PolicyStore policyStore = storeFactory.getPolicyStore();
+ AtomicInteger policiesCount = new AtomicInteger(0);
+ Consumer consumer = createDecisionConsumer(permission, executionContext, decision, policiesCount);
+ Resource resource = permission.getResource();
+
+ if (resource != null) {
+ List extends Policy> resourcePolicies = policyStore.findByResource(resource.getId());
+
+ if (!resourcePolicies.isEmpty()) {
+ resourcePolicies.forEach(consumer);
+ }
+
+ if (resource.getType() != null) {
+ policyStore.findByResourceType(resource.getType(), resourceServer.getId()).forEach(consumer);
+ }
+
+ if (permission.getScopes().isEmpty() && !resource.getScopes().isEmpty()) {
+ policyStore.findByScopeIds(resource.getScopes().stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId()).forEach(consumer);
+ }
+ }
+
+ if (!permission.getScopes().isEmpty()) {
+ policyStore.findByScopeIds(permission.getScopes().stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId()).forEach(consumer);
+ }
+
+ if (PolicyEnforcementMode.PERMISSIVE.equals(resourceServer.getPolicyEnforcementMode()) && policiesCount.get() == 0) {
+ createEvaluation(permission, executionContext, decision, null, null).grant();
+ }
+ }
+
+ private Consumer createDecisionConsumer(ResourcePermission permission, EvaluationContext executionContext, Decision decision, AtomicInteger policiesCount) {
+ return (parentPolicy) -> {
+ if (hasRequestedScopes(permission, parentPolicy)) {
+ for (Policy associatedPolicy : parentPolicy.getAssociatedPolicies()) {
+ PolicyProviderFactory providerFactory = policyProviders.get(associatedPolicy.getType());
+
+ if (providerFactory == null) {
+ throw new RuntimeException("Could not find a policy provider for policy type [" + associatedPolicy.getType() + "].");
+ }
+
+ PolicyProvider policyProvider = providerFactory.create(associatedPolicy, this.authorization);
+
+ if (policyProvider == null) {
+ throw new RuntimeException("Unknown parentPolicy provider for type [" + associatedPolicy.getType() + "].");
+ }
+
+ DefaultEvaluation evaluation = createEvaluation(permission, executionContext, decision, parentPolicy, associatedPolicy);
+
+ policyProvider.evaluate(evaluation);
+ evaluation.denyIfNoEffect();
+
+ policiesCount.incrementAndGet();
+ }
+ }
+ };
+ }
+
+ private DefaultEvaluation createEvaluation(ResourcePermission permission, EvaluationContext executionContext, Decision decision, Policy parentPolicy, Policy associatedPolicy) {
+ return new DefaultEvaluation(permission, executionContext, parentPolicy, associatedPolicy, decision);
+ }
+
+ private boolean hasRequestedScopes(final ResourcePermission permission, final Policy policy) {
+ if (permission.getScopes().isEmpty()) {
+ return true;
+ }
+
+ if (policy.getScopes().isEmpty()) {
+ return true;
+ }
+
+ boolean hasScope = true;
+
+ for (Scope givenScope : policy.getScopes()) {
+ boolean hasGivenScope = false;
+
+ for (Scope scope : permission.getScopes()) {
+ if (givenScope.getId().equals(scope.getId())) {
+ hasGivenScope = true;
+ break;
+ }
+ }
+
+ if (!hasGivenScope) {
+ return false;
+ }
+ }
+
+ return hasScope;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java
new file mode 100644
index 00000000000..f5b08682b70
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java
@@ -0,0 +1,54 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.policy.evaluation;
+
+import org.keycloak.authorization.permission.ResourcePermission;
+
+/**
+ * An {@link Evaluation} is mainly used by {@link org.keycloak.authorization.policy.provider.PolicyProvider} in order to evaluate a single
+ * and specific {@link ResourcePermission} against the configured policies.
+ *
+ * @author Pedro Igor
+ */
+public interface Evaluation {
+
+ /**
+ * Returns the {@link ResourcePermission} to be evaluated.
+ *
+ * @return the permission to be evaluated
+ */
+ ResourcePermission getPermission();
+
+ /**
+ * Returns the {@link EvaluationContext}. Which provides access to the whole evaluation runtime context.
+ *
+ * @return the evaluation context
+ */
+ EvaluationContext getContext();
+
+ /**
+ * Grants the requested permission to the caller.
+ */
+ void grant();
+
+ /**
+ * Denies the requested permission.
+ */
+ void deny();
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/EvaluationContext.java b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/EvaluationContext.java
new file mode 100644
index 00000000000..db5ed0421cb
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/EvaluationContext.java
@@ -0,0 +1,45 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.policy.evaluation;
+
+import org.keycloak.authorization.attribute.Attributes;
+import org.keycloak.authorization.identity.Identity;
+
+/**
+ * This interface serves as a bridge between the policy evaluation runtime and the environment in which it is running. When evaluating
+ * policies, this interface can be used to query information from the execution environment/context and enrich decisions.
+ *
+ * @author Pedro Igor
+ */
+public interface EvaluationContext {
+
+ /**
+ * Returns the {@link Identity} that represents an entity (person or non-person) to which the permissions must be granted, or not.
+ *
+ * @return the identity to which the permissions must be granted, or not
+ */
+ Identity getIdentity();
+
+ /**
+ * Returns all attributes within the current execution and runtime environment.
+ *
+ * @return the attributes within the current execution and runtime environment
+ */
+ Attributes getAttributes();
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/PolicyEvaluator.java b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/PolicyEvaluator.java
new file mode 100644
index 00000000000..c380ba67afd
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/PolicyEvaluator.java
@@ -0,0 +1,38 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.policy.evaluation;
+
+import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.permission.ResourcePermission;
+
+/**
+ *
A {@link PolicyEvaluator} evaluates authorization policies based on a given {@link ResourcePermission}, sending
+ * the results to a {@link Decision} point through the methods defined in that interface.
+ *
+ * @author Pedro Igor
+ */
+public interface PolicyEvaluator {
+
+ /**
+ * Starts the evaluation of the configured authorization policies.
+ *
+ * @param decision a {@link Decision} point to where notifications events will be delivered during the evaluation
+ */
+ void evaluate(ResourcePermission permission, EvaluationContext executionContext, Decision decision);
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/Result.java b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/Result.java
new file mode 100644
index 00000000000..325af3d2496
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/Result.java
@@ -0,0 +1,120 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.policy.evaluation;
+
+import org.keycloak.authorization.Decision.Effect;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.permission.ResourcePermission;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Pedro Igor
+ */
+public class Result {
+
+ private final ResourcePermission permission;
+ private List results = new ArrayList<>();
+ private Effect status;
+
+ public Result(ResourcePermission permission) {
+ this.permission = permission;
+ }
+
+ public ResourcePermission getPermission() {
+ return permission;
+ }
+
+ public List getResults() {
+ return results;
+ }
+
+ public PolicyResult policy(Policy policy) {
+ for (PolicyResult result : this.results) {
+ if (result.getPolicy().equals(policy)) {
+ return result;
+ }
+ }
+
+ PolicyResult policyResult = new PolicyResult(policy);
+
+ this.results.add(policyResult);
+
+ return policyResult;
+ }
+
+ public void setStatus(final Effect status) {
+ this.status = status;
+ }
+
+ public Effect getEffect() {
+ return status;
+ }
+
+ public static class PolicyResult {
+
+ private final Policy policy;
+ private List associatedPolicies = new ArrayList<>();
+ private Effect status;
+
+ public PolicyResult(Policy policy) {
+ this.policy = policy;
+ }
+
+ public PolicyResult status(Effect status) {
+ this.status = status;
+ return this;
+ }
+
+ public PolicyResult policy(Policy policy) {
+ return getPolicy(policy, this.associatedPolicies);
+ }
+
+ private PolicyResult getPolicy(Policy policy, List results) {
+ for (PolicyResult result : results) {
+ if (result.getPolicy().equals(policy)) {
+ return result;
+ }
+ }
+
+ PolicyResult policyResult = new PolicyResult(policy);
+
+ results.add(policyResult);
+
+ return policyResult;
+ }
+
+ public Policy getPolicy() {
+ return policy;
+ }
+
+ public List getAssociatedPolicies() {
+ return associatedPolicies;
+ }
+
+ public Effect getStatus() {
+ return status;
+ }
+
+ public void setStatus(final Effect status) {
+ this.status = status;
+ }
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/package-info.java b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/package-info.java
new file mode 100644
index 00000000000..dcae2ed0e4b
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Provides classes related with the evaluation of policies.
+ *
+ * @author Pedro Igor
+ */
+package org.keycloak.authorization.policy.evaluation;
\ No newline at end of file
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProvider.java b/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProvider.java
new file mode 100644
index 00000000000..2405c3b0b50
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProvider.java
@@ -0,0 +1,29 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.policy.provider;
+
+import org.keycloak.authorization.policy.evaluation.Evaluation;
+import org.keycloak.provider.Provider;
+
+/**
+ * @author Pedro Igor
+ */
+public interface PolicyProvider extends Provider {
+
+ void evaluate(Evaluation evaluation);
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderAdminService.java b/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderAdminService.java
new file mode 100644
index 00000000000..d26208efff0
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderAdminService.java
@@ -0,0 +1,33 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.policy.provider;
+
+import org.keycloak.authorization.model.Policy;
+
+/**
+ * @author Pedro Igor
+ */
+public interface PolicyProviderAdminService {
+
+ void onCreate(Policy policy);
+
+ void onUpdate(Policy policy);
+
+ void onRemove(Policy policy);
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java b/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
new file mode 100644
index 00000000000..1beedd9e5f7
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
@@ -0,0 +1,39 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.policy.provider;
+
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.provider.ProviderEvent;
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author Pedro Igor
+ */
+public interface PolicyProviderFactory extends ProviderFactory {
+
+ String getName();
+
+ String getGroup();
+
+ PolicyProvider create(Policy policy, AuthorizationProvider authorization);
+
+ PolicyProviderAdminService getAdminResource(ResourceServer resourceServer);
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicySpi.java b/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicySpi.java
new file mode 100644
index 00000000000..422981d0c95
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/provider/PolicySpi.java
@@ -0,0 +1,48 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.policy.provider;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author Pedro Igor
+ */
+public class PolicySpi implements Spi {
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "policy";
+ }
+
+ @Override
+ public Class extends Provider> getProviderClass() {
+ return PolicyProvider.class;
+ }
+
+ @Override
+ public Class extends ProviderFactory> getProviderFactoryClass() {
+ return PolicyProviderFactory.class;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/provider/package-info.java b/server-spi/src/main/java/org/keycloak/authorization/policy/provider/package-info.java
new file mode 100644
index 00000000000..6a66949d191
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/provider/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Provides classes and a SPI to plug different policy providers.
+ *
+ * @author Pedro Igor
+ */
+package org.keycloak.authorization.policy.provider;
\ No newline at end of file
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java b/server-spi/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java
new file mode 100644
index 00000000000..dac1b336953
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java
@@ -0,0 +1,63 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.store;
+
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.authorization.store.syncronization.ClientApplicationSynchronizer;
+import org.keycloak.authorization.store.syncronization.RealmSynchronizer;
+import org.keycloak.authorization.store.syncronization.Synchronizer;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel.ClientRemovedEvent;
+import org.keycloak.models.RealmModel.RealmRemovedEvent;
+import org.keycloak.provider.ProviderEvent;
+import org.keycloak.provider.ProviderFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Pedro Igor
+ */
+public interface AuthorizationStoreFactory extends ProviderFactory {
+
+ @Override
+ default void postInit(KeycloakSessionFactory factory) {
+ registerSynchronizationListeners(factory);
+ }
+
+ default void registerSynchronizationListeners(KeycloakSessionFactory factory) {
+ Map, Synchronizer> synchronizers = new HashMap<>();
+
+ synchronizers.put(ClientRemovedEvent.class, new ClientApplicationSynchronizer());
+ synchronizers.put(RealmRemovedEvent.class, new RealmSynchronizer());
+
+ factory.register(event -> {
+ try {
+ synchronizers.forEach((eventType, synchronizer) -> {
+ if (eventType.isInstance(event)) {
+ synchronizer.synchronize(event, factory);
+ }
+ });
+ } catch (Exception e) {
+ throw new RuntimeException("Error synchronizing authorization data.", e);
+ }
+ });
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/PolicyStore.java b/server-spi/src/main/java/org/keycloak/authorization/store/PolicyStore.java
new file mode 100644
index 00000000000..f55db9971ef
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/store/PolicyStore.java
@@ -0,0 +1,119 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.store;
+
+
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
+
+import java.util.List;
+
+/**
+ * A {@link PolicyStore} is responsible to manage the persistence of {@link Policy} instances.
+ *
+ * @author Pedro Igor
+ */
+public interface PolicyStore {
+
+ /**
+ * Creates a new {@link Policy} instance. The new instance is not necessarily persisted though, which may require
+ * a call to the {#save} method to actually make it persistent.
+ *
+ * @param name the name of the policy
+ * @param type the type of the policy
+ * @param resourceServer the resource server to which this policy belongs
+ * @return a new instance of {@link Policy}
+ */
+ Policy create(String name, String type, ResourceServer resourceServer);
+
+ /**
+ * Deletes a policy from the underlying persistence mechanism.
+ *
+ * @param id the id of the policy to delete
+ */
+ void delete(String id);
+
+ /**
+ * Returns a {@link Policy} with the given id
+ *
+ * @param id the identifier of the policy
+ * @return a policy with the given identifier.
+ */
+ Policy findById(String id);
+
+ /**
+ * Returns a {@link Policy} with the given name
+ *
+ * @param name the name of the policy
+ * @param resourceServerId the resource server id
+ * @return a policy with the given name.
+ */
+ Policy findByName(String name, String resourceServerId);
+
+ /**
+ * Returns a list of {@link Policy} associated with a {@link ResourceServer} with the given resourceServerId.
+ *
+ * @param resourceServerId the identifier of a resource server
+ * @return a list of policies that belong to the given resource server
+ */
+ List findByResourceServer(String resourceServerId);
+
+ /**
+ * Returns a list of {@link Policy} associated with a {@link org.keycloak.authorization.core.model.Resource} with the given resourceId.
+ *
+ * @param resourceId the identifier of a resource
+ * @return a list of policies associated with the given resource
+ */
+ List findByResource(String resourceId);
+
+ /**
+ * Returns a list of {@link Policy} associated with a {@link org.keycloak.authorization.core.model.Resource} with the given type.
+ *
+ * @param resourceType the type of a resource
+ * @param resourceServerId the resource server id
+ * @return a list of policies associated with the given resource type
+ */
+ List findByResourceType(String resourceType, String resourceServerId);
+
+ /**
+ * Returns a list of {@link Policy} associated with a {@link org.keycloak.authorization.core.model.Scope} with the given scopeIds.
+ *
+ * @param scopeIds the id of the scopes
+ * @param resourceServerId the resource server id
+ * @return a list of policies associated with the given scopes
+ */
+ List findByScopeIds(List scopeIds, String resourceServerId);
+
+ /**
+ * Returns a list of {@link Policy} with the given type.
+ *
+ * @param type the type of the policy
+ * @return a list of policies with the given type
+ */
+ List findByType(String type);
+
+ /**
+ * Returns a list of {@link Policy} that depends on another policy with the given id.
+ *
+ * @param id the id of the policy to query its dependents
+ * @return a list of policies that depends on the a policy with the given identifier
+ */
+ List findDependentPolicies(String id);
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/ResourceServerStore.java b/server-spi/src/main/java/org/keycloak/authorization/store/ResourceServerStore.java
new file mode 100644
index 00000000000..742f98b299c
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/store/ResourceServerStore.java
@@ -0,0 +1,63 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.store;
+
+import org.keycloak.authorization.model.ResourceServer;
+
+/**
+ * A {@link ResourceServerStore} is responsible to manage the persistence of {@link ResourceServer} instances.
+ *
+ * @author Pedro Igor
+ */
+public interface ResourceServerStore {
+
+ /**
+ * Creates a {@link ResourceServer} instance backed by this persistent storage implementation.
+ *
+ * @param clientId the client id acting as a resource server
+ *
+ * @return an instance backed by the underlying storage implementation
+ */
+ ResourceServer create(String clientId);
+
+ /**
+ * Removes a {@link ResourceServer} instance, with the given {@code id} from the persistent storage.
+ *
+ * @param id the identifier of an existing resource server instance
+ */
+ void delete(String id);
+
+ /**
+ * Returns a {@link ResourceServer} instance based on its identifier.
+ *
+ * @param id the identifier of an existing resource server instance
+ *
+ * @return the resource server instance with the given identifier or null if no instance was found
+ */
+ ResourceServer findById(String id);
+
+ /**
+ * Returns a {@link ResourceServer} instance based on the identifier of a client application.
+ *
+ * @param id the identifier of an existing client application
+ *
+ * @return the resource server instance, with the given client id or null if no instance was found
+ */
+ ResourceServer findByClient(String id);
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/ResourceStore.java b/server-spi/src/main/java/org/keycloak/authorization/store/ResourceStore.java
new file mode 100644
index 00000000000..5b92808b1e7
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/store/ResourceStore.java
@@ -0,0 +1,99 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.store;
+
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A {@link ResourceStore} is responsible to manage the persistence of {@link Resource} instances.
+ *
+ * @author Pedro Igor
+ */
+public interface ResourceStore {
+
+ /**
+ *
Creates a {@link Resource} instance backed by this persistent storage implementation.
+ *
+ * @param name the name of this resource. It must be unique.
+ * @param resourceServer the resource server to where the given resource belongs to
+ * @param owner the owner of this resource or null if the resource server is the owner
+ * @return an instance backed by the underlying storage implementation
+ */
+ Resource create(String name, ResourceServer resourceServer, String owner);
+
+ /**
+ * Removes a {@link Resource} instance, with the given {@code id} from the persistent storage.
+ *
+ * @param id the identifier of an existing resource instance
+ */
+ void delete(String id);
+
+ /**
+ * Returns a {@link Resource} instance based on its identifier.
+ *
+ * @param id the identifier of an existing resource instance
+ * @return the resource instance with the given identifier or null if no instance was found
+ */
+ Resource findById(String id);
+
+ /**
+ * Finds all {@link Resource} instances with the given {@code ownerId}.
+ *
+ * @param ownerId the identifier of the owner
+ * @return a list with all resource instances owned by the given owner
+ */
+ List findByOwner(String ownerId);
+
+ /**
+ * Finds all {@link Resource} instances associated with a given resource server.
+ *
+ * @param resourceServerId the identifier of the resource server
+ * @return a list with all resources associated with the given resource server
+ */
+ List findByResourceServer(String resourceServerId);
+
+ /**
+ * Finds all {@link Resource} associated with a given scope.
+ *
+ * @param id one or more scope identifiers
+ * @return a list of resources associated with the given scope(s)
+ */
+ List findByScope(String... id);
+
+ /**
+ * Find a {@link Resource} by its name.
+ *
+ * @param name the name of the resource
+ * @param resourceServerId the identifier of the resource server
+ * @return a resource with the given name
+ */
+ Resource findByName(String name, String resourceServerId);
+
+ /**
+ * Finds all {@link Resource} with the given type.
+ *
+ * @param type the type of the resource
+ * @return a list of resources with the given type
+ */
+ List findByType(String type);
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/ScopeStore.java b/server-spi/src/main/java/org/keycloak/authorization/store/ScopeStore.java
new file mode 100644
index 00000000000..501217f5e77
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/store/ScopeStore.java
@@ -0,0 +1,78 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.store;
+
+
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
+
+import java.util.List;
+
+/**
+ * A {@link ScopeStore} is responsible to manage the persistence of {@link Scope} instances.
+ *
+ * @author Pedro Igor
+ */
+public interface ScopeStore {
+
+ /**
+ * Creates a new {@link Scope} instance. The new instance is not necessarily persisted though, which may require
+ * a call to the {#save} method to actually make it persistent.
+ *
+ * @param name the name of the scope
+ * @param resourceServer the resource server to which this scope belongs
+ *
+ * @return a new instance of {@link Scope}
+ */
+ Scope create(String name, ResourceServer resourceServer);
+
+ /**
+ * Deletes a scope from the underlying persistence mechanism.
+ *
+ * @param id the id of the scope to delete
+ */
+ void delete(String id);
+
+ /**
+ * Returns a {@link Scope} with the given id
+ *
+ * @param id the identifier of the scope
+ *
+ * @return a scope with the given identifier.
+ */
+ Scope findById(String id);
+
+ /**
+ * Returns a {@link Scope} with the given name
+ *
+ * @param name the name of the scope
+ *
+ * @param resourceServerId
+ * @return a scope with the given name.
+ */
+ Scope findByName(String name, String resourceServerId);
+
+ /**
+ * Returns a list of {@link Scope} associated with a {@link ResourceServer} with the given resourceServerId.
+ *
+ * @param resourceServerId the identifier of a resource server
+ *
+ * @return a list of scopes that belong to the given resource server
+ */
+ List findByResourceServer(String id);
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/StoreFactory.java b/server-spi/src/main/java/org/keycloak/authorization/store/StoreFactory.java
new file mode 100644
index 00000000000..4f50c11c49e
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/store/StoreFactory.java
@@ -0,0 +1,61 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.store;
+
+import org.keycloak.provider.Provider;
+
+/**
+ * A factory for the different types of storages that manage the persistence of the domain model types.
+ *
+ *
Implementations of this interface are usually related with the creation of those storage types accordingly with a
+ * specific persistence mechanism such as relational and NoSQL databases, filesystem, etc.
+ *
+ * @author Pedro Igor
+ */
+public interface StoreFactory extends Provider {
+
+ /**
+ * Returns a {@link ResourceStore}.
+ *
+ * @return the resource store
+ */
+ ResourceStore getResourceStore();
+
+ /**
+ * Returns a {@link ResourceServerStore}.
+ *
+ * @return the resource server store
+ */
+ ResourceServerStore getResourceServerStore();
+
+ /**
+ * Returns a {@link ScopeStore}.
+ *
+ * @return the scope store
+ */
+ ScopeStore getScopeStore();
+
+ /**
+ * Returns a {@link PolicyStore}.
+ *
+ * @return the policy store
+ */
+ PolicyStore getPolicyStore();
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/StoreFactorySpi.java b/server-spi/src/main/java/org/keycloak/authorization/store/StoreFactorySpi.java
new file mode 100644
index 00000000000..53bfb256c84
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/store/StoreFactorySpi.java
@@ -0,0 +1,48 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.store;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author Pedro Igor
+ */
+public class StoreFactorySpi implements Spi {
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "authorizationPersister";
+ }
+
+ @Override
+ public Class extends Provider> getProviderClass() {
+ return StoreFactory.class;
+ }
+
+ @Override
+ public Class extends ProviderFactory> getProviderFactoryClass() {
+ return AuthorizationStoreFactory.class;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/package-info.java b/server-spi/src/main/java/org/keycloak/authorization/store/package-info.java
new file mode 100644
index 00000000000..d9800da36fa
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/store/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Provides classes and a SPI to plug different metadata storage implementations.
+ *
+ * @author Pedro Igor
+ */
+package org.keycloak.authorization.store;
\ No newline at end of file
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/syncronization/ClientApplicationSynchronizer.java b/server-spi/src/main/java/org/keycloak/authorization/store/syncronization/ClientApplicationSynchronizer.java
new file mode 100644
index 00000000000..67683ff9de0
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/store/syncronization/ClientApplicationSynchronizer.java
@@ -0,0 +1,51 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.store.syncronization;
+
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.store.AuthorizationStoreFactory;
+import org.keycloak.authorization.store.ResourceServerStore;
+import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel.ClientRemovedEvent;
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author Pedro Igor
+ */
+public class ClientApplicationSynchronizer implements Synchronizer {
+
+ @Override
+ public void synchronize(ClientRemovedEvent event, KeycloakSessionFactory factory) {
+ ProviderFactory providerFactory = factory.getProviderFactory(AuthorizationProvider.class);
+ AuthorizationProvider authorizationProvider = providerFactory.create(event.getKeycloakSession());
+ StoreFactory storeFactory = authorizationProvider.getStoreFactory();
+ ResourceServerStore store = storeFactory.getResourceServerStore();
+ ResourceServer resourceServer = store.findByClient(event.getClient().getId());
+
+ if (resourceServer != null) {
+ String id = resourceServer.getId();
+ storeFactory.getResourceStore().findByResourceServer(id).forEach(resource -> storeFactory.getResourceStore().delete(resource.getId()));
+ storeFactory.getScopeStore().findByResourceServer(id).forEach(scope -> storeFactory.getScopeStore().delete(scope.getId()));
+ storeFactory.getPolicyStore().findByResourceServer(id).forEach(scope -> storeFactory.getPolicyStore().delete(scope.getId()));
+ storeFactory.getResourceServerStore().delete(id);
+ }
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java b/server-spi/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java
new file mode 100644
index 00000000000..4f0ef32cd6b
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java
@@ -0,0 +1,50 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.store.syncronization;
+
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel.RealmRemovedEvent;
+import org.keycloak.provider.ProviderFactory;
+
+/*
+ * @author Pedro Igor
+ */
+public class RealmSynchronizer implements Synchronizer {
+ @Override
+ public void synchronize(RealmRemovedEvent event, KeycloakSessionFactory factory) {
+ ProviderFactory providerFactory = factory.getProviderFactory(AuthorizationProvider.class);
+ AuthorizationProvider authorizationProvider = providerFactory.create(event.getKeycloakSession());
+ StoreFactory storeFactory = authorizationProvider.getStoreFactory();
+
+ event.getRealm().getClients().forEach(clientModel -> {
+ ResourceServer resourceServer = storeFactory.getResourceServerStore().findByClient(clientModel.getClientId());
+
+ if (resourceServer != null) {
+ String id = resourceServer.getId();
+ storeFactory.getResourceStore().findByResourceServer(id).forEach(resource -> storeFactory.getResourceStore().delete(resource.getId()));
+ storeFactory.getScopeStore().findByResourceServer(id).forEach(scope -> storeFactory.getScopeStore().delete(scope.getId()));
+ storeFactory.getPolicyStore().findByResourceServer(id).forEach(scope -> storeFactory.getPolicyStore().delete(scope.getId()));
+ storeFactory.getResourceServerStore().delete(id);
+ }
+ });
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/syncronization/Synchronizer.java b/server-spi/src/main/java/org/keycloak/authorization/store/syncronization/Synchronizer.java
new file mode 100644
index 00000000000..eb07947dba9
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/authorization/store/syncronization/Synchronizer.java
@@ -0,0 +1,31 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.store.syncronization;
+
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderEvent;
+
+/**
+ * @author Pedro Igor
+ */
+public interface Synchronizer {
+
+ void synchronize(E event, KeycloakSessionFactory factory);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/AdminRoles.java b/server-spi/src/main/java/org/keycloak/models/AdminRoles.java
index 34cdb363425..24455b8bfd6 100755
--- a/server-spi/src/main/java/org/keycloak/models/AdminRoles.java
+++ b/server-spi/src/main/java/org/keycloak/models/AdminRoles.java
@@ -37,13 +37,15 @@ public class AdminRoles {
public static String VIEW_CLIENTS = "view-clients";
public static String VIEW_EVENTS = "view-events";
public static String VIEW_IDENTITY_PROVIDERS = "view-identity-providers";
+ public static String VIEW_AUTHORIZATION = "view-authorization";
public static String MANAGE_REALM = "manage-realm";
public static String MANAGE_USERS = "manage-users";
public static String MANAGE_IDENTITY_PROVIDERS = "manage-identity-providers";
public static String MANAGE_CLIENTS = "manage-clients";
public static String MANAGE_EVENTS = "manage-events";
+ public static String MANAGE_AUTHORIZATION = "manage-authorization";
- public static String[] ALL_REALM_ROLES = {CREATE_CLIENT, VIEW_REALM, VIEW_USERS, VIEW_CLIENTS, VIEW_EVENTS, VIEW_IDENTITY_PROVIDERS, MANAGE_REALM, MANAGE_USERS, MANAGE_CLIENTS, MANAGE_EVENTS, MANAGE_IDENTITY_PROVIDERS};
+ public static String[] ALL_REALM_ROLES = {CREATE_CLIENT, VIEW_REALM, VIEW_USERS, VIEW_CLIENTS, VIEW_EVENTS, VIEW_IDENTITY_PROVIDERS, VIEW_AUTHORIZATION, MANAGE_REALM, MANAGE_USERS, MANAGE_CLIENTS, MANAGE_EVENTS, MANAGE_IDENTITY_PROVIDERS, MANAGE_AUTHORIZATION};
}
diff --git a/server-spi/src/main/java/org/keycloak/models/Constants.java b/server-spi/src/main/java/org/keycloak/models/Constants.java
index 460d08ff1fc..7f998dfe793 100755
--- a/server-spi/src/main/java/org/keycloak/models/Constants.java
+++ b/server-spi/src/main/java/org/keycloak/models/Constants.java
@@ -37,6 +37,10 @@ public interface Constants {
String[] BROKER_SERVICE_ROLES = {READ_TOKEN_ROLE};
String OFFLINE_ACCESS_ROLE = OAuth2Constants.OFFLINE_ACCESS;
+ String AUTHZ_UMA_PROTECTION = "uma_protection";
+ String AUTHZ_UMA_AUTHORIZATION = "uma_authorization";
+ String[] AUTHZ_DEFAULT_AUTHORIZATION_ROLES = {AUTHZ_UMA_AUTHORIZATION};
+
String DEFAULT_HASH_ALGORITHM = "pbkdf2";
// 15 minutes
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
index 1c42e45e51d..9fe36acd423 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
@@ -37,10 +37,20 @@ public interface RealmModel extends RoleContainerModel {
RealmModel getCreatedRealm();
}
+ interface RealmRemovedEvent extends ProviderEvent {
+ RealmModel getRealm();
+ KeycloakSession getKeycloakSession();
+ }
+
interface ClientCreationEvent extends ProviderEvent {
ClientModel getCreatedClient();
}
+ interface ClientRemovedEvent extends ProviderEvent {
+ ClientModel getClient();
+ KeycloakSession getKeycloakSession();
+ }
+
interface UserFederationProviderCreationEvent extends ProviderEvent {
UserFederationProviderModel getCreatedFederationProvider();
RealmModel getRealm();
diff --git a/server-spi/src/main/java/org/keycloak/models/RoleContainerModel.java b/server-spi/src/main/java/org/keycloak/models/RoleContainerModel.java
index 24c60b303d4..00542eb08d6 100755
--- a/server-spi/src/main/java/org/keycloak/models/RoleContainerModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RoleContainerModel.java
@@ -17,6 +17,8 @@
package org.keycloak.models;
+import org.keycloak.provider.ProviderEvent;
+
import java.util.List;
import java.util.Set;
@@ -25,6 +27,12 @@ import java.util.Set;
* @version $Revision: 1 $
*/
public interface RoleContainerModel {
+
+ interface RoleRemovedEvent extends ProviderEvent {
+ RoleModel getRole();
+ KeycloakSession getKeycloakSession();
+ }
+
String getId();
RoleModel getRole(String name);
diff --git a/server-spi/src/main/java/org/keycloak/models/UserModel.java b/server-spi/src/main/java/org/keycloak/models/UserModel.java
index 0453977162d..013ff9ca739 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserModel.java
@@ -17,6 +17,8 @@
package org.keycloak.models;
+import org.keycloak.provider.ProviderEvent;
+
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -32,6 +34,11 @@ public interface UserModel extends RoleMapperModel {
String EMAIL = "email";
String LOCALE = "locale";
+ interface UserRemovedEvent extends ProviderEvent {
+ UserModel getUser();
+ KeycloakSession getKeycloakSession();
+ }
+
String getId();
String getUsername();
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactoryProvider.java b/server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactoryProvider.java
new file mode 100644
index 00000000000..3be3b786569
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactoryProvider.java
@@ -0,0 +1,27 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.models.cache.authorization;
+
+import org.keycloak.authorization.store.StoreFactory;
+
+/**
+ * @author Pedro Igor
+ */
+public interface CachedStoreFactoryProvider extends StoreFactory {
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactorySpi.java b/server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactorySpi.java
new file mode 100644
index 00000000000..226949d1278
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactorySpi.java
@@ -0,0 +1,48 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.models.cache.authorization;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author Pedro Igor
+ */
+public class CachedStoreFactorySpi implements Spi {
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "authz-fached-store-factory";
+ }
+
+ @Override
+ public Class extends Provider> getProviderClass() {
+ return CachedStoreFactoryProvider.class;
+ }
+
+ @Override
+ public Class extends ProviderFactory> getProviderFactoryClass() {
+ return CachedStoreProviderFactory.class;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java b/server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java
new file mode 100644
index 00000000000..b8563cb74cf
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java
@@ -0,0 +1,27 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.models.cache.authorization;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author Pedro Igor
+ */
+public interface CachedStoreProviderFactory extends ProviderFactory {
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 24e3ac8b6aa..0bec4629d6e 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -17,6 +17,9 @@
package org.keycloak.models.utils;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.store.ResourceServerStore;
import org.keycloak.hash.Pbkdf2PasswordHashProvider;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.Constants;
@@ -88,6 +91,8 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import static java.lang.Boolean.TRUE;
+
public class RepresentationToModel {
private static Logger logger = Logger.getLogger(RepresentationToModel.class);
@@ -984,6 +989,21 @@ public class RepresentationToModel {
if (resourceRep.isUseTemplateMappers() != null) client.setUseTemplateMappers(resourceRep.isUseTemplateMappers());
else client.setUseTemplateMappers(resourceRep.getClientTemplate() != null);
+ boolean createResourceServer = TRUE.equals(resourceRep.getAuthorizationServicesEnabled());
+
+ if (createResourceServer) {
+ AuthorizationProvider provider = session.getProvider(AuthorizationProvider.class);
+ ResourceServerStore resourceServerStore = provider.getStoreFactory().getResourceServerStore();
+
+ client.setServiceAccountsEnabled(true);
+ client.setBearerOnly(false);
+ client.setPublicClient(false);
+
+ ResourceServer resourceServer = resourceServerStore.create(client.getId());
+
+ resourceServer.setAllowRemoteResourceManagement(true);
+ resourceServer.setPolicyEnforcementMode(ResourceServer.PolicyEnforcementMode.ENFORCING);
+ }
return client;
}
diff --git a/server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProvider.java b/server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProvider.java
new file mode 100644
index 00000000000..e30f5dab9f8
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProvider.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.protocol.oidc;
+
+import org.keycloak.provider.Provider;
+
+import javax.ws.rs.core.Response;
+
+/**
+ * Provides introspection for a determined OAuth2 token type.
+ *
+ * @author Pedro Igor
+ */
+public interface TokenIntrospectionProvider extends Provider {
+
+ /**
+ * Introspect the token.
+ *
+ * @param token the token to introspect.
+ * @return the response with the information about the token
+ */
+ Response introspect(String token);
+}
diff --git a/server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProviderFactory.java b/server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProviderFactory.java
new file mode 100644
index 00000000000..48b75562571
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProviderFactory.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.protocol.oidc;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * A factory that creates {@link TokenIntrospectionProvider} instances.
+ *
+ * @author Pedro Igor
+ */
+public interface TokenIntrospectionProviderFactory extends ProviderFactory {
+}
diff --git a/server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionSpi.java b/server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionSpi.java
new file mode 100644
index 00000000000..4eb6d398d65
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionSpi.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.protocol.oidc;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * A {@link Spi} to support additional tokens types to the OAuth2 Token Introspection Endpoint.
+ *
+ * @author Pedro Igor
+ */
+public class TokenIntrospectionSpi implements Spi {
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "oauth2-token-introspection";
+ }
+
+ @Override
+ public Class extends Provider> getProviderClass() {
+ return TokenIntrospectionProvider.class;
+ }
+
+ @Override
+ public Class extends ProviderFactory> getProviderFactoryClass() {
+ return TokenIntrospectionProviderFactory.class;
+ }
+}
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 63015cfd3b8..9a8489874d0 100755
--- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -53,4 +53,9 @@ org.keycloak.authentication.RequiredActionSpi
org.keycloak.authentication.FormAuthenticatorSpi
org.keycloak.authentication.FormActionSpi
org.keycloak.cluster.ClusterSpi
+org.keycloak.authorization.policy.provider.PolicySpi
+org.keycloak.authorization.store.StoreFactorySpi
+org.keycloak.authorization.AuthorizationSpi
+org.keycloak.models.cache.authorization.CachedStoreFactorySpi
+org.keycloak.protocol.oidc.TokenIntrospectionSpi
diff --git a/services/pom.xml b/services/pom.xml
index 41ca54d634d..16b19e766c5 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -32,6 +32,8 @@
1.0.5
+ 1.8
+ 1.8
diff --git a/services/src/main/java/org/keycloak/authorization/AuthorizationService.java b/services/src/main/java/org/keycloak/authorization/AuthorizationService.java
new file mode 100644
index 00000000000..f519b40727e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authorization/AuthorizationService.java
@@ -0,0 +1,65 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization;
+
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.authorization.authorization.AuthorizationTokenService;
+import org.keycloak.authorization.entitlement.EntitlementService;
+import org.keycloak.authorization.protection.ProtectionService;
+
+import javax.ws.rs.Path;
+
+/**
+ * @author Pedro Igor
+ */
+public class AuthorizationService {
+
+ private final AuthorizationProvider authorization;
+
+ public AuthorizationService(AuthorizationProvider authorization) {
+ this.authorization = authorization;
+ }
+
+ @Path("/entitlement")
+ public Object getEntitlementService() {
+ EntitlementService service = new EntitlementService(this.authorization);
+
+ ResteasyProviderFactory.getInstance().injectProperties(service);
+
+ return service;
+ }
+
+ @Path("/protection")
+ public Object getProtectionService() {
+ ProtectionService service = new ProtectionService(this.authorization);
+
+ ResteasyProviderFactory.getInstance().injectProperties(service);
+
+ return service;
+ }
+
+ @Path("/authorize")
+ public Object authorize() {
+ AuthorizationTokenService resource = new AuthorizationTokenService(this.authorization);
+
+ ResteasyProviderFactory.getInstance().injectProperties(resource);
+
+ return resource;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java b/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java
new file mode 100644
index 00000000000..5df5a0b08b4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization;
+
+import org.keycloak.Config;
+import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
+
+import java.util.concurrent.Executor;
+
+/**
+ * @author Pedro Igor
+ */
+public class DefaultAuthorizationProviderFactory implements AuthorizationProviderFactory {
+
+ private Executor scheduler;
+
+ @Override
+ public AuthorizationProvider create(KeycloakSession session) {
+ StoreFactory storeFactory = session.getProvider(CachedStoreFactoryProvider.class);
+
+ if (storeFactory == null) {
+ storeFactory = session.getProvider(StoreFactory.class);
+ }
+
+ return new AuthorizationProvider(session, storeFactory);
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ //TODO: user-defined configuration
+// Executor executor = Executors.newWorkStealingPool();
+// this.scheduler = command -> {
+// Map, Object> contextDataMap = ResteasyProviderFactory.getContextDataMap();
+// executor.execute(() -> {
+// ResteasyProviderFactory.pushContextDataMap(contextDataMap);
+// command.run();
+// });
+// };
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return "authorization";
+ }
+}
diff --git a/services/src/main/java/org/keycloak/authorization/ErrorCode.java b/services/src/main/java/org/keycloak/authorization/ErrorCode.java
new file mode 100644
index 00000000000..63ac38a6dc9
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authorization/ErrorCode.java
@@ -0,0 +1,28 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization;
+
+/**
+ * @author Pedro Igor
+ */
+public interface ErrorCode {
+
+ String INVALID_CLIENT_ID = "invalid_client_id";
+
+}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java b/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java
new file mode 100644
index 00000000000..0521c1f655f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java
@@ -0,0 +1,73 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.admin;
+
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.services.resources.admin.RealmAuth;
+
+import javax.ws.rs.Path;
+
+/**
+ * @author Pedro Igor
+ */
+public class AuthorizationService {
+
+ private final RealmAuth auth;
+ private final ClientModel client;
+ private final KeycloakSession session;
+ private final ResourceServer resourceServer;
+ private final AuthorizationProvider authorization;
+
+ public AuthorizationService(KeycloakSession session, ClientModel client, RealmAuth auth) {
+ this.session = session;
+ this.client = client;
+ this.authorization = session.getProvider(AuthorizationProvider.class);
+ this.resourceServer = this.authorization.getStoreFactory().getResourceServerStore().findByClient(this.client.getId());
+ this.auth = auth;
+
+ if (auth != null) {
+ this.auth.init(RealmAuth.Resource.AUTHORIZATION);
+ }
+ }
+
+ @Path("/resource-server")
+ public ResourceServerService resourceServer() {
+ ResourceServerService resource = new ResourceServerService(this.authorization, this.resourceServer, this.client, this.auth);
+
+ ResteasyProviderFactory.getInstance().injectProperties(resource);
+
+ return resource;
+ }
+
+ public void enable() {
+ resourceServer().create();
+ }
+
+ public void disable() {
+ resourceServer().delete();
+ }
+
+ public boolean isEnabled() {
+ return this.resourceServer != null;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
new file mode 100644
index 00000000000..2847d6dc1ac
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
@@ -0,0 +1,205 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.admin;
+
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.admin.representation.PolicyEvaluationRequest;
+import org.keycloak.authorization.admin.representation.PolicyEvaluationResponse;
+import org.keycloak.authorization.attribute.Attributes;
+import org.keycloak.authorization.common.KeycloakEvaluationContext;
+import org.keycloak.authorization.common.KeycloakIdentity;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
+import org.keycloak.authorization.permission.ResourcePermission;
+import org.keycloak.authorization.policy.evaluation.DecisionResultCollector;
+import org.keycloak.authorization.policy.evaluation.EvaluationContext;
+import org.keycloak.authorization.policy.evaluation.Result;
+import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.authorization.util.Permissions;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.services.Urls;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Produces;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.util.Arrays.asList;
+
+/**
+ * @author Pedro Igor
+ */
+public class PolicyEvaluationService {
+
+ private final AuthorizationProvider authorization;
+ @Context
+ private HttpRequest httpRequest;
+
+ private final ResourceServer resourceServer;
+
+ PolicyEvaluationService(ResourceServer resourceServer, AuthorizationProvider authorization) {
+ this.resourceServer = resourceServer;
+ this.authorization = authorization;
+ }
+
+ @POST
+ @Consumes("application/json")
+ @Produces("application/json")
+ public void evaluate(PolicyEvaluationRequest evaluationRequest, @Suspended AsyncResponse asyncResponse) {
+ EvaluationContext evaluationContext = createEvaluationContext(evaluationRequest);
+ authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization), evaluationContext).evaluate(createDecisionCollector(evaluationRequest, authorization, asyncResponse));
+ }
+
+ private DecisionResultCollector createDecisionCollector(PolicyEvaluationRequest evaluationRequest, AuthorizationProvider authorization, AsyncResponse asyncResponse) {
+ return new DecisionResultCollector() {
+ @Override
+ protected void onComplete(List results) {
+ try {
+ asyncResponse.resume(Response.ok(PolicyEvaluationResponse.build(evaluationRequest, results, resourceServer, authorization)).build());
+ } catch (Throwable cause) {
+ asyncResponse.resume(cause);
+ }
+ }
+
+ @Override
+ public void onError(Throwable cause) {
+ asyncResponse.resume(cause);
+ }
+ };
+ }
+
+ private EvaluationContext createEvaluationContext(PolicyEvaluationRequest representation) {
+ return new KeycloakEvaluationContext(createIdentity(representation), this.authorization.getKeycloakSession()) {
+ @Override
+ public Attributes getAttributes() {
+ Map> attributes = new HashMap<>(super.getAttributes().toMap());
+ Map givenAttributes = representation.getContext().get("attributes");
+
+ if (givenAttributes != null) {
+ givenAttributes.forEach((key, entryValue) -> {
+ if (entryValue != null) {
+ List values = new ArrayList();
+
+ for (String value : entryValue.split(",")) {
+ values.add(value);
+ }
+
+ attributes.put(key, values);
+ }
+ });
+ }
+
+ return Attributes.from(attributes);
+ }
+ };
+ }
+
+ private List createPermissions(PolicyEvaluationRequest representation, EvaluationContext evaluationContext, AuthorizationProvider authorization) {
+ if (representation.isEntitlements()) {
+ return Permissions.all(this.resourceServer, evaluationContext.getIdentity(), authorization);
+ }
+
+ return representation.getResources().stream().flatMap((Function>) resource -> {
+ Set givenScopes = resource.getScopes();
+
+ if (givenScopes == null) {
+ givenScopes = new HashSet();
+ }
+
+ StoreFactory storeFactory = authorization.getStoreFactory();
+
+ List scopes = givenScopes.stream().map(scopeName -> storeFactory.getScopeStore().findByName(scopeName, this.resourceServer.getId())).collect(Collectors.toList());
+
+ if (resource.getId() != null) {
+ Resource resourceModel = storeFactory.getResourceStore().findById(resource.getId());
+ return Stream.of(new ResourcePermission(resourceModel, scopes, resourceServer));
+ } else if (resource.getType() != null) {
+ return storeFactory.getResourceStore().findByType(resource.getType()).stream().map(resource1 -> new ResourcePermission(resource1, scopes, resourceServer));
+ } else {
+ return scopes.stream().map(scope -> new ResourcePermission(null, asList(scope), resourceServer));
+ }
+ }).collect(Collectors.toList());
+ }
+
+ private KeycloakIdentity createIdentity(PolicyEvaluationRequest representation) {
+ RealmModel realm = this.authorization.getKeycloakSession().getContext().getRealm();
+ AccessToken accessToken = new AccessToken();
+
+ accessToken.subject(representation.getUserId());
+ accessToken.issuedFor(representation.getClientId());
+ accessToken.audience(representation.getClientId());
+ accessToken.issuer(Urls.realmIssuer(this.authorization.getKeycloakSession().getContext().getUri().getBaseUri(), realm.getName()));
+ accessToken.setRealmAccess(new AccessToken.Access());
+
+ Map claims = accessToken.getOtherClaims();
+ Map givenAttributes = representation.getContext().get("attributes");
+
+ if (givenAttributes != null) {
+ givenAttributes.forEach((key, value) -> claims.put(key, asList(value)));
+ }
+
+ String subject = accessToken.getSubject();
+
+ if (subject != null) {
+ UserModel userModel = this.authorization.getKeycloakSession().users().getUserById(subject, realm);
+
+ if (userModel != null) {
+ Set roleMappings = userModel.getRoleMappings();
+
+ roleMappings.stream().map(RoleModel::getName).forEach(roleName -> accessToken.getRealmAccess().addRole(roleName));
+
+ String clientId = representation.getClientId();
+
+ if (clientId != null) {
+ ClientModel clientModel = realm.getClientById(clientId);
+
+ accessToken.addAccess(clientModel.getClientId());
+
+ userModel.getClientRoleMappings(clientModel).stream().map(RoleModel::getName).forEach(roleName -> accessToken.getResourceAccess(clientModel.getClientId()).addRole(roleName));
+
+ //TODO: would be awesome if we could transform the access token using the configured protocol mappers. Tried, but without a clientSession and userSession is tuff.
+ }
+ }
+ }
+
+ if (representation.getRoleIds() != null) {
+ representation.getRoleIds().forEach(roleName -> accessToken.getRealmAccess().addRole(roleName));
+ }
+
+ return new KeycloakIdentity(accessToken, this.authorization.getKeycloakSession());
+ }
+}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
new file mode 100644
index 00000000000..cffba23d6ab
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
@@ -0,0 +1,356 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.authorization.admin;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.admin.representation.PolicyProviderRepresentation;
+import org.keycloak.authorization.admin.representation.PolicyRepresentation;
+import org.keycloak.authorization.admin.util.Models;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
+import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
+import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
+import org.keycloak.authorization.store.PolicyStore;
+import org.keycloak.authorization.store.StoreFactory;
+
+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.Response;
+import javax.ws.rs.core.Response.Status;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.stream.Collectors;
+
+import static org.keycloak.authorization.admin.util.Models.toRepresentation;
+
+/**
+ * @author Pedro Igor
+ */
+public class PolicyService {
+
+ private final ResourceServer resourceServer;
+ private final AuthorizationProvider authorization;
+
+ public PolicyService(ResourceServer resourceServer, AuthorizationProvider authorization) {
+ this.resourceServer = resourceServer;
+ this.authorization = authorization;
+ }
+
+ @POST
+ @Consumes("application/json")
+ @Produces("application/json")
+ public Response create(PolicyRepresentation representation) {
+ Policy policy = Models.toModel(representation, this.resourceServer, authorization);
+
+ updateResources(policy, authorization);
+ updateAssociatedPolicies(policy);
+ updateScopes(policy, authorization);
+
+ PolicyProviderAdminService resource = getPolicyProviderAdminResource(policy.getType(), authorization);
+
+ if (resource != null) {
+ try {
+ resource.onCreate(policy);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ representation.setId(policy.getId());
+
+ return Response.status(Status.CREATED).entity(representation).build();
+ }
+
+ @Path("{id}")
+ @PUT
+ @Consumes("application/json")
+ @Produces("application/json")
+ public Response update(@PathParam("id") String id, PolicyRepresentation representation) {
+ representation.setId(id);
+ StoreFactory storeFactory = authorization.getStoreFactory();
+ Policy policy = storeFactory.getPolicyStore().findById(representation.getId());
+
+ if (policy == null) {
+ return Response.status(Status.NOT_FOUND).build();
+ }
+
+ policy.setName(representation.getName());
+ policy.setDescription(representation.getDescription());
+ policy.setConfig(representation.getConfig());
+ policy.setDecisionStrategy(representation.getDecisionStrategy());
+ policy.setLogic(representation.getLogic());
+
+ updateResources(policy, authorization);
+ updateAssociatedPolicies(policy);
+ updateScopes(policy, authorization);
+
+ PolicyProviderAdminService resource = getPolicyProviderAdminResource(policy.getType(), authorization);
+
+ if (resource != null) {
+ try {
+ resource.onUpdate(policy);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return Response.status(Status.CREATED).build();
+ }
+
+ @Path("{id}")
+ @DELETE
+ public Response delete(@PathParam("id") String id) {
+ StoreFactory storeFactory = authorization.getStoreFactory();
+ PolicyStore policyStore = storeFactory.getPolicyStore();
+ Policy policy = policyStore.findById(id);
+
+ if (policy == null) {
+ return Response.status(Status.NOT_FOUND).build();
+ }
+
+ PolicyProviderAdminService resource = getPolicyProviderAdminResource(policy.getType(), authorization);
+
+ if (resource != null) {
+ try {
+ resource.onRemove(policy);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ policyStore.findDependentPolicies(id).forEach(dependentPolicy -> {
+ dependentPolicy.removeAssociatedPolicy(policy);
+ });
+
+ policyStore.delete(policy.getId());
+
+ return Response.noContent().build();
+ }
+
+ @Path("{id}")
+ @GET
+ @Produces("application/json")
+ public Response findById(@PathParam("id") String id) {
+ StoreFactory storeFactory = authorization.getStoreFactory();
+ Policy model = storeFactory.getPolicyStore().findById(id);
+
+ if (model == null) {
+ return Response.status(Status.NOT_FOUND).build();
+ }
+
+ return Response.ok(toRepresentation(model, authorization)).build();
+ }
+
+ @GET
+ @Produces("application/json")
+ public Response findAll() {
+ StoreFactory storeFactory = authorization.getStoreFactory();
+ return Response.ok(
+ storeFactory.getPolicyStore().findByResourceServer(resourceServer.getId()).stream()
+ .map(policy -> toRepresentation(policy, authorization))
+ .collect(Collectors.toList()))
+ .build();
+ }
+
+ @Path("providers")
+ @GET
+ @Produces("application/json")
+ public Response findPolicyProviders() {
+ return Response.ok(
+ authorization.getProviderFactories().stream()
+ .map(provider -> {
+ PolicyProviderRepresentation representation = new PolicyProviderRepresentation();
+
+ representation.setName(provider.getName());
+ representation.setGroup(provider.getGroup());
+ representation.setType(provider.getId());
+
+ return representation;
+ })
+ .collect(Collectors.toList()))
+ .build();
+ }
+
+ @Path("evaluate")
+ public PolicyEvaluationService getPolicyEvaluateResource() {
+ PolicyEvaluationService resource = new PolicyEvaluationService(this.resourceServer, this.authorization);
+
+ ResteasyProviderFactory.getInstance().injectProperties(resource);
+
+ return resource;
+ }
+
+ @Path("{policyType}")
+ public Object getPolicyTypeResource(@PathParam("policyType") String policyType) {
+ return getPolicyProviderAdminResource(policyType, this.authorization);
+ }
+
+ private PolicyProviderAdminService getPolicyProviderAdminResource(String policyType, AuthorizationProvider authorization) {
+ PolicyProviderFactory providerFactory = authorization.getProviderFactory(policyType);
+
+ if (providerFactory != null) {
+ return providerFactory.getAdminResource(this.resourceServer);
+ }
+
+ return null;
+ }
+
+ private void updateScopes(Policy policy, AuthorizationProvider authorization) {
+ String scopes = policy.getConfig().get("scopes");
+ if (scopes != null) {
+ String[] scopeIds;
+
+ try {
+ scopeIds = new ObjectMapper().readValue(scopes, String[].class);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ StoreFactory storeFactory = authorization.getStoreFactory();
+ PolicyStore policyStore = storeFactory.getPolicyStore();
+
+ for (String scopeId : scopeIds) {
+ boolean hasScope = false;
+
+ for (Scope scopeModel : new HashSet(policy.getScopes())) {
+ if (scopeModel.getId().equals(scopeId)) {
+ hasScope = true;
+ }
+ }
+ if (!hasScope) {
+ policy.addScope(storeFactory.getScopeStore().findById(scopeId));
+ }
+ }
+
+ for (Scope scopeModel : new HashSet(policy.getScopes())) {
+ boolean hasScope = false;
+
+ for (String scopeId : scopeIds) {
+ if (scopeModel.getId().equals(scopeId)) {
+ hasScope = true;
+ }
+ }
+ if (!hasScope) {
+ policy.removeScope(scopeModel);
+ }
+ }
+ }
+ }
+
+ private void updateAssociatedPolicies(Policy policy) {
+ String policies = policy.getConfig().get("applyPolicies");
+
+ if (policies != null) {
+ String[] policyIds;
+
+ try {
+ policyIds = new ObjectMapper().readValue(policies, String[].class);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ StoreFactory storeFactory = authorization.getStoreFactory();
+ PolicyStore policyStore = storeFactory.getPolicyStore();
+
+ for (String policyId : policyIds) {
+ boolean hasPolicy = false;
+
+ for (Policy policyModel : new HashSet(policy.getAssociatedPolicies())) {
+ if (policyModel.getId().equals(policyId)) {
+ hasPolicy = true;
+ }
+ }
+
+
+ if (!hasPolicy) {
+ Policy associatedPolicy = policyStore.findById(policyId);
+
+ if (associatedPolicy == null) {
+ associatedPolicy = policyStore.findByName(policyId, this.resourceServer.getId());
+ }
+
+ policy.addAssociatedPolicy(associatedPolicy);
+ }
+ }
+
+ for (Policy policyModel : new HashSet