Merge pull request #5079 from pedroigor/KEYCLOAK-6529
[KEYCLOAK-6529] - Resource Attributes
This commit is contained in:
commit
ffeb0420bf
21 changed files with 549 additions and 8 deletions
|
@ -20,6 +20,8 @@ package org.keycloak.authorization.client.representation;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -49,6 +51,7 @@ public class ResourceRepresentation {
|
||||||
private String iconUri;
|
private String iconUri;
|
||||||
private String owner;
|
private String owner;
|
||||||
private Boolean ownerManagedAccess;
|
private Boolean ownerManagedAccess;
|
||||||
|
private Map<String, List<String>> attributes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* Creates a new instance.
|
||||||
|
@ -204,4 +207,12 @@ public class ResourceRepresentation {
|
||||||
", scopes=" + scopes +
|
", scopes=" + scopes +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAttributes(Map<String, List<String>> attributes) {
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<String>> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,14 @@ import java.net.URI;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import org.keycloak.json.StringListMapDeserializer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>One or more resources that the resource server manages as a set of protected resources.
|
* <p>One or more resources that the resource server manages as a set of protected resources.
|
||||||
|
@ -53,6 +56,10 @@ public class ResourceRepresentation {
|
||||||
private List<PolicyRepresentation> policies;
|
private List<PolicyRepresentation> policies;
|
||||||
|
|
||||||
private String displayName;
|
private String displayName;
|
||||||
|
|
||||||
|
@JsonDeserialize(using = StringListMapDeserializer.class)
|
||||||
|
private Map<String, List<String>> attributes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* Creates a new instance.
|
||||||
*
|
*
|
||||||
|
@ -195,6 +202,14 @@ public class ResourceRepresentation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, List<String>> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttributes(Map<String, List<String>> attributes) {
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.keycloak.models.cache.infinispan.authorization.entities.CachedResourc
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ import java.util.stream.Collectors;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||||
|
|
||||||
protected CachedResource cached;
|
protected CachedResource cached;
|
||||||
protected StoreFactoryCacheSession cacheSession;
|
protected StoreFactoryCacheSession cacheSession;
|
||||||
protected Resource updated;
|
protected Resource updated;
|
||||||
|
@ -210,6 +212,50 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||||
updated.updateScopes(scopes);
|
updated.updateScopes(scopes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getAttributes() {
|
||||||
|
if (updated != null) return updated.getAttributes();
|
||||||
|
return cached.getAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSingleAttribute(String name) {
|
||||||
|
if (updated != null) return updated.getSingleAttribute(name);
|
||||||
|
|
||||||
|
List<String> values = cached.getAttributes().getOrDefault(name, Collections.emptyList());
|
||||||
|
|
||||||
|
if (values.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getAttribute(String name) {
|
||||||
|
if (updated != null) return updated.getAttribute(name);
|
||||||
|
|
||||||
|
List<String> values = cached.getAttributes().getOrDefault(name, Collections.emptyList());
|
||||||
|
|
||||||
|
if (values.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttribute(String name, List<String> values) {
|
||||||
|
getDelegateForUpdate();
|
||||||
|
updated.setAttribute(name, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAttribute(String name) {
|
||||||
|
getDelegateForUpdate();
|
||||||
|
updated.removeAttribute(name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -20,8 +20,11 @@ package org.keycloak.models.cache.infinispan.authorization.entities;
|
||||||
|
|
||||||
import org.keycloak.authorization.model.Resource;
|
import org.keycloak.authorization.model.Resource;
|
||||||
import org.keycloak.authorization.model.Scope;
|
import org.keycloak.authorization.model.Scope;
|
||||||
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
|
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -39,6 +42,7 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
|
||||||
private String uri;
|
private String uri;
|
||||||
private Set<String> scopesIds;
|
private Set<String> scopesIds;
|
||||||
private boolean ownerManagedAccess;
|
private boolean ownerManagedAccess;
|
||||||
|
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
|
||||||
|
|
||||||
public CachedResource(Long revision, Resource resource) {
|
public CachedResource(Long revision, Resource resource) {
|
||||||
super(revision, resource.getId());
|
super(revision, resource.getId());
|
||||||
|
@ -51,6 +55,7 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
|
||||||
this.resourceServerId = resource.getResourceServer().getId();
|
this.resourceServerId = resource.getResourceServer().getId();
|
||||||
this.scopesIds = resource.getScopes().stream().map(Scope::getId).collect(Collectors.toSet());
|
this.scopesIds = resource.getScopes().stream().map(Scope::getId).collect(Collectors.toSet());
|
||||||
ownerManagedAccess = resource.isOwnerManagedAccess();
|
ownerManagedAccess = resource.isOwnerManagedAccess();
|
||||||
|
this.attributes.putAll(resource.getAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,4 +94,8 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
|
||||||
public Set<String> getScopesIds() {
|
public Set<String> getScopesIds() {
|
||||||
return this.scopesIds;
|
return this.scopesIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, List<String>> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 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.authorization.jpa.entities;
|
||||||
|
|
||||||
|
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.ManyToOne;
|
||||||
|
import javax.persistence.NamedQueries;
|
||||||
|
import javax.persistence.NamedQuery;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
@NamedQueries({
|
||||||
|
@NamedQuery(name="deleteResourceAttributesByNameAndResource", query="delete from ResourceAttributeEntity attr where attr.resource.id = :resourceId and attr.name = :name")
|
||||||
|
})
|
||||||
|
@Table(name="RESOURCE_ATTRIBUTE")
|
||||||
|
@Entity
|
||||||
|
public class ResourceAttributeEntity {
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
@ManyToOne(fetch= FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "RESOURCE_ID")
|
||||||
|
private ResourceEntity resource;
|
||||||
|
|
||||||
|
@Column(name = "NAME")
|
||||||
|
private String name;
|
||||||
|
@Column(name = "VALUE")
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceEntity getResource() {
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResource(ResourceEntity resource) {
|
||||||
|
this.resource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null) return false;
|
||||||
|
if (!(o instanceof ResourceAttributeEntity)) return false;
|
||||||
|
|
||||||
|
ResourceAttributeEntity that = (ResourceAttributeEntity) o;
|
||||||
|
|
||||||
|
if (!id.equals(that.getId())) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return id.hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ package org.keycloak.authorization.jpa.entities;
|
||||||
|
|
||||||
import javax.persistence.Access;
|
import javax.persistence.Access;
|
||||||
import javax.persistence.AccessType;
|
import javax.persistence.AccessType;
|
||||||
|
import javax.persistence.CascadeType;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.FetchType;
|
import javax.persistence.FetchType;
|
||||||
|
@ -30,11 +31,18 @@ import javax.persistence.ManyToMany;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.NamedQueries;
|
import javax.persistence.NamedQueries;
|
||||||
import javax.persistence.NamedQuery;
|
import javax.persistence.NamedQuery;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
import javax.persistence.UniqueConstraint;
|
import javax.persistence.UniqueConstraint;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.Fetch;
|
||||||
|
import org.hibernate.annotations.FetchMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
*/
|
*/
|
||||||
|
@ -94,6 +102,10 @@ public class ResourceEntity {
|
||||||
@JoinTable(name = "RESOURCE_POLICY", joinColumns = @JoinColumn(name = "RESOURCE_ID"), inverseJoinColumns = @JoinColumn(name = "POLICY_ID"))
|
@JoinTable(name = "RESOURCE_POLICY", joinColumns = @JoinColumn(name = "RESOURCE_ID"), inverseJoinColumns = @JoinColumn(name = "POLICY_ID"))
|
||||||
private List<PolicyEntity> policies = new LinkedList<>();
|
private List<PolicyEntity> policies = new LinkedList<>();
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="resource")
|
||||||
|
@Fetch(FetchMode.SUBSELECT)
|
||||||
|
private Collection<ResourceAttributeEntity> attributes = new ArrayList<>();
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
@ -179,6 +191,14 @@ public class ResourceEntity {
|
||||||
this.policies = policies;
|
this.policies = policies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collection<ResourceAttributeEntity> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttributes(Collection<ResourceAttributeEntity> attributes) {
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -16,20 +16,27 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.authorization.jpa.store;
|
package org.keycloak.authorization.jpa.store;
|
||||||
|
|
||||||
|
import org.keycloak.authorization.jpa.entities.ResourceAttributeEntity;
|
||||||
import org.keycloak.authorization.jpa.entities.ResourceEntity;
|
import org.keycloak.authorization.jpa.entities.ResourceEntity;
|
||||||
import org.keycloak.authorization.jpa.entities.ScopeEntity;
|
import org.keycloak.authorization.jpa.entities.ScopeEntity;
|
||||||
import org.keycloak.authorization.model.Resource;
|
import org.keycloak.authorization.model.Resource;
|
||||||
import org.keycloak.authorization.model.ResourceServer;
|
import org.keycloak.authorization.model.ResourceServer;
|
||||||
import org.keycloak.authorization.model.Scope;
|
import org.keycloak.authorization.model.Scope;
|
||||||
import org.keycloak.authorization.store.StoreFactory;
|
import org.keycloak.authorization.store.StoreFactory;
|
||||||
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.models.jpa.JpaModel;
|
import org.keycloak.models.jpa.JpaModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.Query;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,6 +44,7 @@ import java.util.Set;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
|
public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
|
||||||
|
|
||||||
private ResourceEntity entity;
|
private ResourceEntity entity;
|
||||||
private EntityManager em;
|
private EntityManager em;
|
||||||
private StoreFactory storeFactory;
|
private StoreFactory storeFactory;
|
||||||
|
@ -158,6 +166,72 @@ public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getAttributes() {
|
||||||
|
MultivaluedHashMap<String, String> result = new MultivaluedHashMap<>();
|
||||||
|
for (ResourceAttributeEntity attr : entity.getAttributes()) {
|
||||||
|
result.add(attr.getName(), attr.getValue());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSingleAttribute(String name) {
|
||||||
|
List<String> values = getAttributes().getOrDefault(name, Collections.emptyList());
|
||||||
|
|
||||||
|
if (values.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getAttribute(String name) {
|
||||||
|
List<String> values = getAttributes().getOrDefault(name, Collections.emptyList());
|
||||||
|
|
||||||
|
if (values.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttribute(String name, List<String> values) {
|
||||||
|
removeAttribute(name);
|
||||||
|
|
||||||
|
for (String value : values) {
|
||||||
|
ResourceAttributeEntity attr = new ResourceAttributeEntity();
|
||||||
|
attr.setId(KeycloakModelUtils.generateId());
|
||||||
|
attr.setName(name);
|
||||||
|
attr.setValue(value);
|
||||||
|
attr.setResource(entity);
|
||||||
|
em.persist(attr);
|
||||||
|
entity.getAttributes().add(attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAttribute(String name) {
|
||||||
|
Query query = em.createNamedQuery("deleteResourceAttributesByNameAndResource");
|
||||||
|
|
||||||
|
query.setParameter("name", name);
|
||||||
|
query.setParameter("resourceId", entity.getId());
|
||||||
|
|
||||||
|
query.executeUpdate();
|
||||||
|
|
||||||
|
List<ResourceAttributeEntity> toRemove = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ResourceAttributeEntity attr : entity.getAttributes()) {
|
||||||
|
if (attr.getName().equals(name)) {
|
||||||
|
toRemove.add(attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.getAttributes().removeAll(toRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static ResourceEntity toEntity(EntityManager em, Resource resource) {
|
public static ResourceEntity toEntity(EntityManager em, Resource resource) {
|
||||||
if (resource instanceof ResourceAdapter) {
|
if (resource instanceof ResourceAdapter) {
|
||||||
|
|
|
@ -74,5 +74,21 @@
|
||||||
<constraints nullable="false"/>
|
<constraints nullable="false"/>
|
||||||
</column>
|
</column>
|
||||||
</addColumn>
|
</addColumn>
|
||||||
|
|
||||||
|
<createTable tableName="RESOURCE_ATTRIBUTE">
|
||||||
|
<column name="ID" type="VARCHAR(36)" defaultValue="sybase-needs-something-here">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="NAME" type="VARCHAR(255)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="VALUE" type="VARCHAR(255)"/>
|
||||||
|
<column name="RESOURCE_ID" type="VARCHAR(36)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey columnNames="ID" constraintName="RES_ATTR_PK" tableName="RESOURCE_ATTRIBUTE"/>
|
||||||
|
<addForeignKeyConstraint baseColumnNames="RESOURCE_ID" baseTableName="RESOURCE_ATTRIBUTE" constraintName="FK_5HRM2VLF9QL5FU022KQEPOVBR" referencedColumnNames="ID" referencedTableName="RESOURCE_SERVER_RESOURCE"/>
|
||||||
</changeSet>
|
</changeSet>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
<class>org.keycloak.authorization.jpa.entities.ScopeEntity</class>
|
<class>org.keycloak.authorization.jpa.entities.ScopeEntity</class>
|
||||||
<class>org.keycloak.authorization.jpa.entities.PolicyEntity</class>
|
<class>org.keycloak.authorization.jpa.entities.PolicyEntity</class>
|
||||||
<class>org.keycloak.authorization.jpa.entities.PermissionTicketEntity</class>
|
<class>org.keycloak.authorization.jpa.entities.PermissionTicketEntity</class>
|
||||||
|
<class>org.keycloak.authorization.jpa.entities.ResourceAttributeEntity</class>
|
||||||
|
|
||||||
<!-- User Federation Storage -->
|
<!-- User Federation Storage -->
|
||||||
<class>org.keycloak.storage.jpa.entity.BrokerLinkEntity</class>
|
<class>org.keycloak.storage.jpa.entity.BrokerLinkEntity</class>
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package org.keycloak.authorization.model;
|
package org.keycloak.authorization.model;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -126,8 +127,56 @@ public interface Resource {
|
||||||
*/
|
*/
|
||||||
String getOwner();
|
String getOwner();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this resource can be managed by the resource owner.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this resource can be managed by the resource owner. Otherwise, {@code false}.
|
||||||
|
*/
|
||||||
boolean isOwnerManagedAccess();
|
boolean isOwnerManagedAccess();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets if this resource can be managed by the resource owner.
|
||||||
|
*
|
||||||
|
* @param ownerManagedAccess {@code true} indicates that this resource can be managed by the resource owner.
|
||||||
|
*/
|
||||||
void setOwnerManagedAccess(boolean ownerManagedAccess);
|
void setOwnerManagedAccess(boolean ownerManagedAccess);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the set of scopes associated with this resource.
|
||||||
|
*
|
||||||
|
* @param scopes the list of scopes to update
|
||||||
|
*/
|
||||||
void updateScopes(Set<Scope> scopes);
|
void updateScopes(Set<Scope> scopes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the attributes associated with this resource.
|
||||||
|
*
|
||||||
|
* @return a map holding the attributes associated with this resource
|
||||||
|
*/
|
||||||
|
Map<String, List<String>> getAttributes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first value of an attribute with the given <code>name</code>
|
||||||
|
*
|
||||||
|
* @return the first value of an attribute
|
||||||
|
*/
|
||||||
|
String getSingleAttribute(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the values of an attribute with the given <code>name</code>
|
||||||
|
*
|
||||||
|
* @return the values of an attribute
|
||||||
|
*/
|
||||||
|
List<String> getAttribute(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an attribute with the given <code>name</code> and <code>values</code>.
|
||||||
|
*
|
||||||
|
* @param name the attribute name
|
||||||
|
* @param value the attribute values
|
||||||
|
* @return a map holding the attributes associated with this resource
|
||||||
|
*/
|
||||||
|
void setAttribute(String name, List<String> values);
|
||||||
|
|
||||||
|
void removeAttribute(String name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -841,6 +841,8 @@ public class ModelToRepresentation {
|
||||||
}
|
}
|
||||||
return scope;
|
return scope;
|
||||||
}).collect(Collectors.toSet()));
|
}).collect(Collectors.toSet()));
|
||||||
|
|
||||||
|
resource.setAttributes(new HashMap<>(model.getAttributes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return resource;
|
return resource;
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -2351,6 +2352,24 @@ public class RepresentationToModel {
|
||||||
existing.updateScopes(resource.getScopes().stream()
|
existing.updateScopes(resource.getScopes().stream()
|
||||||
.map((ScopeRepresentation scope) -> toModel(scope, resourceServer, authorization))
|
.map((ScopeRepresentation scope) -> toModel(scope, resourceServer, authorization))
|
||||||
.collect(Collectors.toSet()));
|
.collect(Collectors.toSet()));
|
||||||
|
Map<String, List<String>> attributes = resource.getAttributes();
|
||||||
|
|
||||||
|
if (attributes != null) {
|
||||||
|
Set<String> existingAttrNames = existing.getAttributes().keySet();
|
||||||
|
|
||||||
|
for (String name : existingAttrNames) {
|
||||||
|
if (attributes.containsKey(name)) {
|
||||||
|
existing.setAttribute(name, attributes.get(name));
|
||||||
|
attributes.remove(name);
|
||||||
|
} else {
|
||||||
|
existing.removeAttribute(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String name : attributes.keySet()) {
|
||||||
|
existing.setAttribute(name, attributes.get(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
@ -2369,6 +2388,14 @@ public class RepresentationToModel {
|
||||||
model.updateScopes(scopes.stream().map((Function<ScopeRepresentation, Scope>) scope -> toModel(scope, resourceServer, authorization)).collect(Collectors.toSet()));
|
model.updateScopes(scopes.stream().map((Function<ScopeRepresentation, Scope>) scope -> toModel(scope, resourceServer, authorization)).collect(Collectors.toSet()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, List<String>> attributes = resource.getAttributes();
|
||||||
|
|
||||||
|
if (attributes != null) {
|
||||||
|
for (Entry<String, List<String>> entry : attributes.entrySet()) {
|
||||||
|
model.setAttribute(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resource.setId(model.getId());
|
resource.setId(model.getId());
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
|
|
|
@ -294,6 +294,22 @@ public class ResourceSetService {
|
||||||
return Response.ok(representation).build();
|
return Response.ok(representation).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Path("{id}/attributes")
|
||||||
|
@GET
|
||||||
|
@NoCache
|
||||||
|
@Produces("application/json")
|
||||||
|
public Response getAttributes(@PathParam("id") String id) {
|
||||||
|
requireView();
|
||||||
|
StoreFactory storeFactory = authorization.getStoreFactory();
|
||||||
|
Resource model = storeFactory.getResourceStore().findById(id, resourceServer.getId());
|
||||||
|
|
||||||
|
if (model == null) {
|
||||||
|
return Response.status(Status.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.ok(model.getAttributes()).build();
|
||||||
|
}
|
||||||
|
|
||||||
@Path("/search")
|
@Path("/search")
|
||||||
@GET
|
@GET
|
||||||
@NoCache
|
@NoCache
|
||||||
|
|
|
@ -150,6 +150,8 @@ public class ResourceService {
|
||||||
return scopeRepresentation;
|
return scopeRepresentation;
|
||||||
}).collect(Collectors.toSet()));
|
}).collect(Collectors.toSet()));
|
||||||
|
|
||||||
|
resource.setAttributes(umaResource.getAttributes());
|
||||||
|
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,6 +180,8 @@ public class ResourceService {
|
||||||
return umaScopeRep;
|
return umaScopeRep;
|
||||||
}).collect(Collectors.toSet()));
|
}).collect(Collectors.toSet()));
|
||||||
|
|
||||||
|
resource.setAttributes(model.getAttributes());
|
||||||
|
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,6 +51,8 @@ public class UmaResourceRepresentation {
|
||||||
private String owner;
|
private String owner;
|
||||||
private Boolean ownerManagedAccess;
|
private Boolean ownerManagedAccess;
|
||||||
|
|
||||||
|
private Map<String, List<String>> attributes;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* Creates a new instance.
|
||||||
|
@ -161,4 +165,12 @@ public class UmaResourceRepresentation {
|
||||||
public Boolean getOwnerManagedAccess() {
|
public Boolean getOwnerManagedAccess() {
|
||||||
return ownerManagedAccess;
|
return ownerManagedAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, List<String>> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttributes(Map<String, List<String>> attributes) {
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,12 @@ import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||||
|
|
||||||
import javax.ws.rs.NotFoundException;
|
import javax.ws.rs.NotFoundException;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
@ -53,6 +57,17 @@ public class ResourceManagementTest extends AbstractAuthorizationTest {
|
||||||
assertEquals("/test/*", newResource.getUri());
|
assertEquals("/test/*", newResource.getUri());
|
||||||
assertEquals("test-resource", newResource.getType());
|
assertEquals("test-resource", newResource.getType());
|
||||||
assertEquals("icon-test-resource", newResource.getIconUri());
|
assertEquals("icon-test-resource", newResource.getIconUri());
|
||||||
|
|
||||||
|
Map<String, List<String>> attributes = newResource.getAttributes();
|
||||||
|
|
||||||
|
assertEquals(2, attributes.size());
|
||||||
|
|
||||||
|
assertTrue(attributes.containsKey("a"));
|
||||||
|
assertTrue(attributes.containsKey("b"));
|
||||||
|
assertTrue(attributes.get("a").containsAll(Arrays.asList("a1", "a2", "a3")));
|
||||||
|
assertEquals(3, attributes.get("a").size());
|
||||||
|
assertTrue(attributes.get("b").containsAll(Arrays.asList("b1")));
|
||||||
|
assertEquals(1, attributes.get("b").size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -105,11 +120,28 @@ public class ResourceManagementTest extends AbstractAuthorizationTest {
|
||||||
resource.setIconUri("changed");
|
resource.setIconUri("changed");
|
||||||
resource.setUri("changed");
|
resource.setUri("changed");
|
||||||
|
|
||||||
|
Map<String, List<String>> attributes = resource.getAttributes();
|
||||||
|
|
||||||
|
attributes.remove("a");
|
||||||
|
attributes.put("c", Arrays.asList("c1", "c2"));
|
||||||
|
attributes.put("b", Arrays.asList("changed"));
|
||||||
|
|
||||||
resource = doUpdateResource(resource);
|
resource = doUpdateResource(resource);
|
||||||
|
|
||||||
assertEquals("changed", resource.getIconUri());
|
assertEquals("changed", resource.getIconUri());
|
||||||
assertEquals("changed", resource.getType());
|
assertEquals("changed", resource.getType());
|
||||||
assertEquals("changed", resource.getUri());
|
assertEquals("changed", resource.getUri());
|
||||||
|
|
||||||
|
attributes = resource.getAttributes();
|
||||||
|
|
||||||
|
assertEquals(2, attributes.size());
|
||||||
|
|
||||||
|
assertFalse(attributes.containsKey("a"));
|
||||||
|
assertTrue(attributes.containsKey("b"));
|
||||||
|
assertTrue(attributes.get("b").containsAll(Arrays.asList("changed")));
|
||||||
|
assertEquals(1, attributes.get("b").size());
|
||||||
|
assertTrue(attributes.get("c").containsAll(Arrays.asList("c1", "c2")));
|
||||||
|
assertEquals(2, attributes.get("c").size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = NotFoundException.class)
|
@Test(expected = NotFoundException.class)
|
||||||
|
@ -205,6 +237,13 @@ public class ResourceManagementTest extends AbstractAuthorizationTest {
|
||||||
newResource.setIconUri(iconUri);
|
newResource.setIconUri(iconUri);
|
||||||
newResource.setOwner(owner != null ? new ResourceOwnerRepresentation(owner) : null);
|
newResource.setOwner(owner != null ? new ResourceOwnerRepresentation(owner) : null);
|
||||||
|
|
||||||
|
Map<String, List<String>> attributes = new HashMap<>();
|
||||||
|
|
||||||
|
attributes.put("a", Arrays.asList("a1", "a2", "a3"));
|
||||||
|
attributes.put("b", Arrays.asList("b1"));
|
||||||
|
|
||||||
|
newResource.setAttributes(attributes);
|
||||||
|
|
||||||
return doCreateResource(newResource);
|
return doCreateResource(newResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,8 @@ public class ResourceManagementWithAuthzClientTest extends ResourceManagementTes
|
||||||
return scope;
|
return scope;
|
||||||
}).collect(Collectors.toSet()));
|
}).collect(Collectors.toSet()));
|
||||||
|
|
||||||
|
resourceRepresentation.setAttributes(created.getAttributes());
|
||||||
|
|
||||||
return resourceRepresentation;
|
return resourceRepresentation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +110,8 @@ public class ResourceManagementWithAuthzClientTest extends ResourceManagementTes
|
||||||
return scope;
|
return scope;
|
||||||
}).collect(Collectors.toSet()));
|
}).collect(Collectors.toSet()));
|
||||||
|
|
||||||
|
resource.setAttributes(newResource.getAttributes());
|
||||||
|
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.keycloak.authorization.attribute.Attributes;
|
||||||
import org.keycloak.authorization.common.DefaultEvaluationContext;
|
import org.keycloak.authorization.common.DefaultEvaluationContext;
|
||||||
import org.keycloak.authorization.identity.Identity;
|
import org.keycloak.authorization.identity.Identity;
|
||||||
import org.keycloak.authorization.model.Policy;
|
import org.keycloak.authorization.model.Policy;
|
||||||
|
import org.keycloak.authorization.model.Resource;
|
||||||
import org.keycloak.authorization.model.ResourceServer;
|
import org.keycloak.authorization.model.ResourceServer;
|
||||||
import org.keycloak.authorization.permission.ResourcePermission;
|
import org.keycloak.authorization.permission.ResourcePermission;
|
||||||
import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
|
import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
|
||||||
|
@ -556,9 +557,49 @@ public class PolicyEvaluationTest extends AbstractAuthzTest {
|
||||||
Assert.assertEquals(Effect.PERMIT, evaluation.getEffect());
|
Assert.assertEquals(Effect.PERMIT, evaluation.getEffect());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@Test
|
||||||
|
public void testCheckResourceAttributes() {
|
||||||
|
testingClient.server().run(PolicyEvaluationTest::testCheckResourceAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void testCheckResourceAttributes(KeycloakSession session) {
|
||||||
|
session.getContext().setRealm(session.realms().getRealmByName("authz-test"));
|
||||||
|
AuthorizationProvider authorization = session.getProvider(AuthorizationProvider.class);
|
||||||
|
ClientModel clientModel = session.realms().getClientByClientId("resource-server-test", session.getContext().getRealm());
|
||||||
|
StoreFactory storeFactory = authorization.getStoreFactory();
|
||||||
|
ResourceServer resourceServer = storeFactory.getResourceServerStore().findById(clientModel.getId());
|
||||||
|
JSPolicyRepresentation policyRepresentation = new JSPolicyRepresentation();
|
||||||
|
|
||||||
|
policyRepresentation.setName("testCheckResourceAttributes");
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.append("var permission = $evaluation.getPermission();");
|
||||||
|
builder.append("var resource = permission.getResource();");
|
||||||
|
builder.append("var attributes = resource.getAttributes();");
|
||||||
|
builder.append("if (attributes.size() == 2 && attributes.containsKey('a1') && attributes.containsKey('a2') && attributes.get('a1').size() == 2 && attributes.get('a2').get(0).equals('3') && resource.getAttribute('a1').size() == 2 && resource.getSingleAttribute('a2').equals('3')) { $evaluation.grant(); }");
|
||||||
|
|
||||||
|
policyRepresentation.setCode(builder.toString());
|
||||||
|
|
||||||
|
Policy policy = storeFactory.getPolicyStore().create(policyRepresentation, resourceServer);
|
||||||
|
PolicyProvider provider = authorization.getProvider(policy.getType());
|
||||||
|
Resource resource = storeFactory.getResourceStore().create("testCheckResourceAttributesResource", resourceServer, resourceServer.getId());
|
||||||
|
|
||||||
|
resource.setAttribute("a1", Arrays.asList("1", "2"));
|
||||||
|
resource.setAttribute("a2", Arrays.asList("3"));
|
||||||
|
|
||||||
|
DefaultEvaluation evaluation = createEvaluation(session, authorization, resource, resourceServer, policy);
|
||||||
|
|
||||||
|
provider.evaluate(evaluation);
|
||||||
|
|
||||||
|
Assert.assertEquals(Effect.PERMIT, evaluation.getEffect());
|
||||||
|
}
|
||||||
|
|
||||||
private static DefaultEvaluation createEvaluation(KeycloakSession session, AuthorizationProvider authorization, ResourceServer resourceServer, Policy policy) {
|
private static DefaultEvaluation createEvaluation(KeycloakSession session, AuthorizationProvider authorization, ResourceServer resourceServer, Policy policy) {
|
||||||
return new DefaultEvaluation(new ResourcePermission(null, null, resourceServer), new DefaultEvaluationContext(new Identity() {
|
return createEvaluation(session, authorization, null, resourceServer, policy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DefaultEvaluation createEvaluation(KeycloakSession session, AuthorizationProvider authorization, Resource resource, ResourceServer resourceServer, Policy policy) {
|
||||||
|
return new DefaultEvaluation(new ResourcePermission(resource, null, resourceServer), new DefaultEvaluationContext(new Identity() {
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return null;
|
return null;
|
||||||
|
@ -568,11 +609,8 @@ public class PolicyEvaluationTest extends AbstractAuthzTest {
|
||||||
public Attributes getAttributes() {
|
public Attributes getAttributes() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}, session), policy, policy, new Decision() {
|
}, session), policy, policy, evaluation -> {
|
||||||
@Override
|
|
||||||
public void onDecision(Evaluation evaluation) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}, authorization);
|
}, authorization);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1183,6 +1183,10 @@ authz-resource-owner.tooltip=The owner of this resource.
|
||||||
authz-resource-type.tooltip=The type of this resource. It can be used to group different resource instances with the same type.
|
authz-resource-type.tooltip=The type of this resource. It can be used to group different resource instances with the same type.
|
||||||
authz-resource-uri.tooltip=An URI that can also be used to uniquely identify this resource.
|
authz-resource-uri.tooltip=An URI that can also be used to uniquely identify this resource.
|
||||||
authz-resource-scopes.tooltip=The scopes associated with this resource.
|
authz-resource-scopes.tooltip=The scopes associated with this resource.
|
||||||
|
authz-resource-attributes=Resource Attributes
|
||||||
|
authz-resource-attributes.tooltip=The attributes associated wth the resource.
|
||||||
|
authz-resource-user-managed-access-enabled=User-Managed Access Enabled
|
||||||
|
authz-resource-user-managed-access-enabled.tooltip=If enabled this access to this resource can be managed by the resource owner.
|
||||||
|
|
||||||
# Authz Scope List
|
# Authz Scope List
|
||||||
authz-add-scope=Add Scope
|
authz-add-scope=Add Scope
|
||||||
|
|
|
@ -294,6 +294,7 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
|
||||||
|
|
||||||
var resource = {};
|
var resource = {};
|
||||||
resource.scopes = [];
|
resource.scopes = [];
|
||||||
|
resource.attributes = {};
|
||||||
|
|
||||||
$scope.resource = angular.copy(resource);
|
$scope.resource = angular.copy(resource);
|
||||||
|
|
||||||
|
@ -328,6 +329,10 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
|
||||||
data.scopes = [];
|
data.scopes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!data.attributes) {
|
||||||
|
data.attributes = {};
|
||||||
|
}
|
||||||
|
|
||||||
$scope.resource = angular.copy(data);
|
$scope.resource = angular.copy(data);
|
||||||
$scope.changed = false;
|
$scope.changed = false;
|
||||||
|
|
||||||
|
@ -343,6 +348,15 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
|
||||||
for (i = 0; i < $scope.resource.scopes.length; i++) {
|
for (i = 0; i < $scope.resource.scopes.length; i++) {
|
||||||
delete $scope.resource.scopes[i].text;
|
delete $scope.resource.scopes[i].text;
|
||||||
}
|
}
|
||||||
|
for (var [key, value] of Object.entries($scope.resource.attributes)) {
|
||||||
|
var values = value.toString().split(',');
|
||||||
|
|
||||||
|
$scope.resource.attributes[key] = [];
|
||||||
|
|
||||||
|
for (j = 0; j < values.length; j++) {
|
||||||
|
$scope.resource.attributes[key].push(values[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
$instance.checkNameAvailability(function () {
|
$instance.checkNameAvailability(function () {
|
||||||
ResourceServerResource.update({realm : realm.realm, client : $scope.client.id, rsrid : $scope.resource._id}, $scope.resource, function() {
|
ResourceServerResource.update({realm : realm.realm, client : $scope.client.id, rsrid : $scope.resource._id}, $scope.resource, function() {
|
||||||
$route.reload();
|
$route.reload();
|
||||||
|
@ -383,6 +397,15 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.addAttribute = function() {
|
||||||
|
$scope.resource.attributes[$scope.newAttribute.key] = $scope.newAttribute.value;
|
||||||
|
delete $scope.newAttribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.removeAttribute = function(key) {
|
||||||
|
delete $scope.resource.attributes[key];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var Scopes = {
|
var Scopes = {
|
||||||
|
|
|
@ -67,11 +67,38 @@
|
||||||
<kc-tooltip>{{:: 'authz-icon-uri.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'authz-icon-uri.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="resource.ownerManagedAccess">User-Managed Access Enabled</label>
|
<label class="col-md-2 control-label" for="resource.ownerManagedAccess">{{:: 'authz-resource-user-managed-access-enabled' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="resource.ownerManagedAccess" id="resource.ownerManagedAccess" onoffswitch />
|
<input ng-model="resource.ownerManagedAccess" id="resource.ownerManagedAccess" onoffswitch />
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>{{:: 'authz-permission-resource-apply-to-resource-type.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'authz-resource-user-managed-access-enabled.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label">{{:: 'authz-resource-attributes' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{:: 'key' | translate}}</th>
|
||||||
|
<th>{{:: 'value' | translate}}</th>
|
||||||
|
<th>{{:: 'actions' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="(key, value) in resource.attributes | toOrderedMapSortedByKey">
|
||||||
|
<td>{{key}}</td>
|
||||||
|
<td><input ng-model="resource.attributes[key]" class="form-control" type="text" name="{{key}}" id="attribute-{{key}}" /></td>
|
||||||
|
<td class="kc-action-cell" id="removeAttribute" data-ng-click="removeAttribute(key)">{{:: 'delete' | translate}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input ng-model="newAttribute.key" class="form-control" type="text" id="newAttributeKey" /></td>
|
||||||
|
<td><input ng-model="newAttribute.value" class="form-control" type="text" id="newAttributeValue" /></td>
|
||||||
|
<td class="kc-action-cell" id="addAttribute" data-ng-click="addAttribute()" data-ng-disabled="!newAttribute.key.length || !newAttribute.value.length">{{:: 'add' | translate}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'authz-resource-attributes.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue