KEYCLOAK-7269 Setting more uris for Authorization Resource

This commit is contained in:
mhajas 2018-07-03 09:56:25 +02:00 committed by Pedro Igor
parent fba2bf0b2f
commit 5aebc74f8c
37 changed files with 523 additions and 124 deletions

View file

@ -225,12 +225,13 @@ public class PolicyEnforcer {
for (String id : protectedResource.findAll()) {
ResourceRepresentation resourceDescription = protectedResource.findById(id);
if (resourceDescription.getUri() != null) {
PathConfig pathConfig = PathConfig.createPathConfig(resourceDescription);
if (resourceDescription.getUris() != null && !resourceDescription.getUris().isEmpty()) {
for(PathConfig pathConfig : PathConfig.createPathConfigs(resourceDescription)) {
paths.put(pathConfig.getPath(), pathConfig);
}
}
}
}
return paths;
}
@ -277,7 +278,7 @@ public class PolicyEnforcer {
cipConfig = pathConfig.getClaimInformationPointConfig();
}
pathConfig = PathConfig.createPathConfig(matchingResources.get(0));
pathConfig = PathConfig.createPathConfigs(matchingResources.get(0)).iterator().next();
if (cipConfig != null) {
pathConfig.setClaimInformationPointConfig(cipConfig);
@ -323,7 +324,7 @@ public class PolicyEnforcer {
if (!search.isEmpty()) {
ResourceRepresentation targetResource = search.get(0);
PathConfig config = PathConfig.createPathConfig(targetResource);
PathConfig config = PathConfig.createPathConfigs(targetResource).iterator().next();
config.setScopes(originalConfig.getScopes());
config.setMethods(originalConfig.getMethods());

View file

@ -27,6 +27,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@ -116,14 +119,16 @@ public class PolicyEnforcerConfig {
public static class PathConfig {
public static PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
public static Set<PathConfig> createPathConfigs(ResourceRepresentation resourceDescription) {
Set<PathConfig> pathConfigs = new HashSet<>();
for (String uri : resourceDescription.getUris()) {
PathConfig pathConfig = new PathConfig();
pathConfig.setId(resourceDescription.getId());
pathConfig.setName(resourceDescription.getName());
String uri = resourceDescription.getUri();
if (uri == null || "".equals(uri.trim())) {
throw new RuntimeException("Failed to configure paths. Resource [" + resourceDescription.getName() + "] has an invalid or empty URI [" + uri + "].");
}
@ -139,7 +144,10 @@ public class PolicyEnforcerConfig {
pathConfig.setScopes(scopeNames);
pathConfig.setType(resourceDescription.getType());
return pathConfig;
pathConfigs.add(pathConfig);
}
return pathConfigs;
}
private String name;

View file

@ -44,7 +44,9 @@ public class ResourceRepresentation {
private String id;
private String name;
private String uri;
@JsonProperty("uris")
private Set<String> uris;
private String type;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonProperty("scopes")
@ -64,29 +66,37 @@ public class ResourceRepresentation {
* Creates a new instance.
*
* @param name a human-readable string describing a set of one or more resources
* @param uri a {@link URI} that provides the network location for the resource set being registered
* @param uris a {@link List} of {@link URI} that provides network locations for the resource set being registered
* @param type a string uniquely identifying the semantics of the resource set
* @param scopes the available scopes for this resource set
* @param iconUri a {@link URI} for a graphic icon representing the resource set
*/
public ResourceRepresentation(String name, Set<ScopeRepresentation> scopes, String uri, String type, String iconUri) {
public ResourceRepresentation(String name, Set<ScopeRepresentation> scopes, Set<String> uris, String type, String iconUri) {
this.name = name;
this.scopes = scopes;
this.uri = uri;
this.uris = uris;
this.type = type;
this.iconUri = iconUri;
}
public ResourceRepresentation(String name, Set<ScopeRepresentation> scopes, String uri, String type, String iconUri) {
this(name, scopes, Collections.singleton(uri), type, iconUri);
}
/**
* Creates a new instance.
*
* @param name a human-readable string describing a set of one or more resources
* @param uri a {@link URI} that provides the network location for the resource set being registered
* @param uris a {@link List} of {@link URI} that provides the network location for the resource set being registered
* @param type a string uniquely identifying the semantics of the resource set
* @param scopes the available scopes for this resource set
*/
public ResourceRepresentation(String name, Set<ScopeRepresentation> scopes, Set<String> uris, String type) {
this(name, scopes, uris, type, null);
}
public ResourceRepresentation(String name, Set<ScopeRepresentation> scopes, String uri, String type) {
this(name, scopes, uri, type, null);
this(name, scopes, Collections.singleton(uri), type, null);
}
/**
@ -97,7 +107,7 @@ public class ResourceRepresentation {
* @param scopes the available scopes for this resource set
*/
public ResourceRepresentation(String name, Set<ScopeRepresentation> scopes) {
this(name, scopes, null, null, null);
this(name, scopes, (Set<String>) null, null, null);
}
public ResourceRepresentation(String name, String... scopes) {
@ -114,7 +124,7 @@ public class ResourceRepresentation {
*
*/
public ResourceRepresentation() {
this(null, null, null, null, null);
this(null, null, (Set<String>) null, null, null);
}
public void setId(String id) {
@ -133,8 +143,18 @@ public class ResourceRepresentation {
return displayName;
}
@Deprecated
@JsonIgnore
public String getUri() {
return this.uri;
if (this.uris == null || this.uris.isEmpty()) {
return null;
}
return this.uris.iterator().next();
}
public Set<String> getUris() {
return this.uris;
}
public String getType() {
@ -161,12 +181,35 @@ public class ResourceRepresentation {
this.displayName = displayName;
}
@Deprecated
public void setUri(String uri) {
if (uri != null && !"".equalsIgnoreCase(uri.trim())) {
this.uri = uri;
this.uris = Collections.singleton(uri);
}
}
public void setUris(Set<String> uris) {
if (uris != null) {
Set<String> resultSet = new HashSet<>();
for (String uri : uris) {
if (uri != null && !"".equalsIgnoreCase(uri.trim())) {
resultSet.add(uri);
}
}
this.uris = resultSet;
}
}
@JsonProperty("uri")
public void addUri(String uri) {
if (this.uris == null) {
this.uris = new HashSet<>();
}
uris.add(uri);
}
public void setType(String type) {
if (type != null && !"".equalsIgnoreCase(type.trim())) {
this.type = type;

View file

@ -49,7 +49,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public Resource getDelegateForUpdate() {
if (updated == null) {
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
updated = cacheSession.getResourceStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
if (updated == null) throw new IllegalStateException("Not found in database");
}
@ -98,7 +98,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setName(String name) {
getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUris(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
updated.setName(name);
}
@ -111,7 +111,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setDisplayName(String name) {
getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUris(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
updated.setDisplayName(name);
}
@ -134,16 +134,16 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
}
@Override
public String getUri() {
if (isUpdated()) return updated.getUri();
return cached.getUri();
public Set<String> getUris() {
if (isUpdated()) return updated.getUris();
return cached.getUris();
}
@Override
public void setUri(String uri) {
public void updateUris(Set<String> uris) {
getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uri, cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
updated.setUri(uri);
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uris, cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
updated.updateUris(uris);
}
@Override
@ -155,7 +155,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setType(String type) {
getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUris(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
updated.setType(type);
}
@ -189,7 +189,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setOwnerManagedAccess(boolean ownerManagedAccess) {
getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
updated.setOwnerManagedAccess(ownerManagedAccess);
}
@ -208,7 +208,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
}
}
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId(), cached.getOwner());
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId(), cached.getOwner());
updated.updateScopes(scopes);
}

View file

@ -77,7 +77,7 @@ public class StoreFactoryCacheManager extends CacheManager {
addInvalidations(InScopePredicate.create().scope(id), invalidations);
}
public void resourceUpdated(String id, String name, String type, String uri, Set<String> scopes, String serverId, String owner, Set<String> invalidations) {
public void resourceUpdated(String id, String name, String type, Set<String> uris, Set<String> scopes, String serverId, String owner, Set<String> invalidations) {
invalidations.add(id);
invalidations.add(StoreFactoryCacheSession.getResourceByNameCacheKey(name, owner, serverId));
invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId));
@ -89,9 +89,11 @@ public class StoreFactoryCacheManager extends CacheManager {
addInvalidations(InResourcePredicate.create().resource(type), invalidations);
}
if (uri != null) {
if (uris != null) {
for (String uri: uris) {
invalidations.add(StoreFactoryCacheSession.getResourceByUriCacheKey(uri, serverId));
}
}
if (scopes != null) {
for (String scope : scopes) {
@ -101,8 +103,8 @@ public class StoreFactoryCacheManager extends CacheManager {
}
}
public void resourceRemoval(String id, String name, String type, String uri, String owner, Set<String> scopes, String serverId, Set<String> invalidations) {
resourceUpdated(id, name, type, uri, scopes, serverId, owner, invalidations);
public void resourceRemoval(String id, String name, String type, Set<String> uris, String owner, Set<String> scopes, String serverId, Set<String> invalidations) {
resourceUpdated(id, name, type, uris, scopes, serverId, owner, invalidations);
addInvalidations(InResourcePredicate.create().resource(id), invalidations);
}

View file

@ -264,12 +264,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
invalidationEvents.add(ScopeUpdatedEvent.create(id, name, serverId));
}
public void registerResourceInvalidation(String id, String name, String type, String uri, Set<String> scopes, String serverId, String owner) {
cache.resourceUpdated(id, name, type, uri, scopes, serverId, owner, invalidations);
public void registerResourceInvalidation(String id, String name, String type, Set<String> uris, Set<String> scopes, String serverId, String owner) {
cache.resourceUpdated(id, name, type, uris, scopes, serverId, owner, invalidations);
ResourceAdapter adapter = managedResources.get(id);
if (adapter != null) adapter.invalidateFlag();
invalidationEvents.add(ResourceUpdatedEvent.create(id, name, type, uri, scopes, serverId, owner));
invalidationEvents.add(ResourceUpdatedEvent.create(id, name, type, uris, scopes, serverId, owner));
}
public void registerPolicyInvalidation(String id, String name, Set<String> resources, Set<String> scopes, String defaultResourceType, String serverId) {
@ -550,7 +550,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
public Resource create(String id, String name, ResourceServer resourceServer, String owner) {
Resource resource = getResourceStoreDelegate().create(id, name, resourceServer, owner);
Resource cached = findById(resource.getId(), resourceServer.getId());
registerResourceInvalidation(resource.getId(), resource.getName(), resource.getType(), resource.getUri(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resourceServer.getId(), resource.getOwner());
registerResourceInvalidation(resource.getId(), resource.getName(), resource.getType(), resource.getUris(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resourceServer.getId(), resource.getOwner());
return cached;
}
@ -561,8 +561,8 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
if (resource == null) return;
cache.invalidateObject(id);
invalidationEvents.add(ResourceRemovedEvent.create(id, resource.getName(), resource.getType(), resource.getUri(), resource.getOwner(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resource.getResourceServer().getId()));
cache.resourceRemoval(id, resource.getName(), resource.getType(), resource.getUri(), resource.getOwner(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resource.getResourceServer().getId(), invalidations);
invalidationEvents.add(ResourceRemovedEvent.create(id, resource.getName(), resource.getType(), resource.getUris(), resource.getOwner(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resource.getResourceServer().getId()));
cache.resourceRemoval(id, resource.getName(), resource.getType(), resource.getUris(), resource.getOwner(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resource.getResourceServer().getId(), invalidations);
getResourceStoreDelegate().delete(id);
}

View file

@ -39,7 +39,7 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
private String type;
private String name;
private String displayName;
private String uri;
private Set<String> uris;
private Set<String> scopesIds;
private boolean ownerManagedAccess;
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
@ -48,7 +48,7 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
super(revision, resource.getId());
this.name = resource.getName();
this.displayName = resource.getDisplayName();
this.uri = resource.getUri();
this.uris = resource.getUris();
this.type = resource.getType();
this.owner = resource.getOwner();
this.iconUri = resource.getIconUri();
@ -67,8 +67,8 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
return this.displayName;
}
public String getUri() {
return this.uri;
public Set<String> getUris() {
return this.uris;
}
public String getType() {

View file

@ -41,15 +41,15 @@ public class ResourceRemovedEvent extends InvalidationEvent implements Authoriza
private String owner;
private String serverId;
private String type;
private String uri;
private Set<String> uris;
private Set<String> scopes;
public static ResourceRemovedEvent create(String id, String name, String type, String uri, String owner, Set<String> scopes, String serverId) {
public static ResourceRemovedEvent create(String id, String name, String type, Set<String> uris, String owner, Set<String> scopes, String serverId) {
ResourceRemovedEvent event = new ResourceRemovedEvent();
event.id = id;
event.name = name;
event.type = type;
event.uri = uri;
event.uris = uris;
event.owner = owner;
event.scopes = scopes;
event.serverId = serverId;
@ -68,7 +68,7 @@ public class ResourceRemovedEvent extends InvalidationEvent implements Authoriza
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
cache.resourceRemoval(id, name, type, uri, owner, scopes, serverId, invalidations);
cache.resourceRemoval(id, name, type, uris, owner, scopes, serverId, invalidations);
}
public static class ExternalizerImpl implements Externalizer<ResourceRemovedEvent> {
@ -82,7 +82,7 @@ public class ResourceRemovedEvent extends InvalidationEvent implements Authoriza
MarshallUtil.marshallString(obj.id, output);
MarshallUtil.marshallString(obj.name, output);
MarshallUtil.marshallString(obj.type, output);
MarshallUtil.marshallString(obj.uri, output);
KeycloakMarshallUtil.writeCollection(obj.uris, KeycloakMarshallUtil.STRING_EXT, output);
MarshallUtil.marshallString(obj.owner, output);
KeycloakMarshallUtil.writeCollection(obj.scopes, KeycloakMarshallUtil.STRING_EXT, output);
MarshallUtil.marshallString(obj.serverId, output);
@ -103,7 +103,7 @@ public class ResourceRemovedEvent extends InvalidationEvent implements Authoriza
res.id = MarshallUtil.unmarshallString(input);
res.name = MarshallUtil.unmarshallString(input);
res.type = MarshallUtil.unmarshallString(input);
res.uri = MarshallUtil.unmarshallString(input);
res.uris = KeycloakMarshallUtil.readCollection(input, KeycloakMarshallUtil.STRING_EXT, HashSet::new);
res.owner = MarshallUtil.unmarshallString(input);
res.scopes = KeycloakMarshallUtil.readCollection(input, KeycloakMarshallUtil.STRING_EXT, HashSet::new);
res.serverId = MarshallUtil.unmarshallString(input);

View file

@ -40,16 +40,16 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
private String name;
private String serverId;
private String type;
private String uri;
private Set<String> uris;
private Set<String> scopes;
private String owner;
public static ResourceUpdatedEvent create(String id, String name, String type, String uri, Set<String> scopes, String serverId, String owner) {
public static ResourceUpdatedEvent create(String id, String name, String type, Set<String> uris, Set<String> scopes, String serverId, String owner) {
ResourceUpdatedEvent event = new ResourceUpdatedEvent();
event.id = id;
event.name = name;
event.type = type;
event.uri = uri;
event.uris = uris;
event.scopes = scopes;
event.serverId = serverId;
event.owner = owner;
@ -68,7 +68,7 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
cache.resourceUpdated(id, name, type, uri, scopes, serverId, owner, invalidations);
cache.resourceUpdated(id, name, type, uris, scopes, serverId, owner, invalidations);
}
public static class ExternalizerImpl implements Externalizer<ResourceUpdatedEvent> {
@ -82,7 +82,7 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
MarshallUtil.marshallString(obj.id, output);
MarshallUtil.marshallString(obj.name, output);
MarshallUtil.marshallString(obj.type, output);
MarshallUtil.marshallString(obj.uri, output);
KeycloakMarshallUtil.writeCollection(obj.uris, KeycloakMarshallUtil.STRING_EXT, output);
KeycloakMarshallUtil.writeCollection(obj.scopes, KeycloakMarshallUtil.STRING_EXT, output);
MarshallUtil.marshallString(obj.serverId, output);
MarshallUtil.marshallString(obj.owner, output);
@ -103,7 +103,7 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
res.id = MarshallUtil.unmarshallString(input);
res.name = MarshallUtil.unmarshallString(input);
res.type = MarshallUtil.unmarshallString(input);
res.uri = MarshallUtil.unmarshallString(input);
res.uris = KeycloakMarshallUtil.readCollection(input, KeycloakMarshallUtil.STRING_EXT, HashSet::new);
res.scopes = KeycloakMarshallUtil.readCollection(input, KeycloakMarshallUtil.STRING_EXT, HashSet::new);
res.serverId = MarshallUtil.unmarshallString(input);
res.owner = MarshallUtil.unmarshallString(input);

View file

@ -21,7 +21,9 @@ package org.keycloak.authorization.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
@ -37,8 +39,10 @@ import javax.persistence.UniqueConstraint;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
@ -54,7 +58,7 @@ import org.hibernate.annotations.FetchMode;
{
@NamedQuery(name="findResourceIdByOwner", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.owner = :owner"),
@NamedQuery(name="findAnyResourceIdByOwner", query="select r.id from ResourceEntity r where r.owner = :owner"),
@NamedQuery(name="findResourceIdByUri", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.uri = :uri"),
@NamedQuery(name="findResourceIdByUri", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and :uri in elements(r.uris)"),
@NamedQuery(name="findResourceIdByName", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.owner = :ownerId and r.name = :name"),
@NamedQuery(name="findResourceIdByType", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.owner = :ownerId and r.type = :type"),
@NamedQuery(name="findResourceIdByServerId", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId "),
@ -75,8 +79,10 @@ public class ResourceEntity {
@Column(name = "DISPLAY_NAME")
private String displayName;
@Column(name = "URI")
private String uri;
@ElementCollection(fetch = FetchType.EAGER)
@Column(name = "VALUE")
@CollectionTable(name = "RESOURCE_URIS", joinColumns = { @JoinColumn(name="RESOURCE_ID") })
private Set<String> uris = new HashSet<>();
@Column(name = "TYPE")
private String type;
@ -130,12 +136,12 @@ public class ResourceEntity {
this.displayName = displayName;
}
public String getUri() {
return uri;
public Set<String> getUris() {
return uris;
}
public void setUri(String uri) {
this.uri = uri;
public void setUris(Set<String> uris) {
this.uris = uris;
}
public String getType() {

View file

@ -31,6 +31,7 @@ import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
@ -192,9 +193,12 @@ public class JPAResourceStore implements ResourceStore {
} else if ("ownerManagedAccess".equals(name)) {
predicates.add(builder.equal(root.get(name), Boolean.valueOf(value[0])));
} else if ("uri".equals(name)) {
predicates.add(builder.equal(builder.lower(root.get(name)), value[0].toLowerCase()));
predicates.add(builder.lower(root.join("uris")).in(value[0].toLowerCase()));
} else if ("uri_not_null".equals(name)) {
predicates.add(builder.isNotNull(root.get("uri")));
// predicates.add(builder.isNotEmpty(root.get("uris"))); looks like there is a bug in hibernate and this line doesn't work: https://hibernate.atlassian.net/browse/HHH-6686
// Workaround
Expression<Integer> urisSize = builder.size(root.get("uris"));
predicates.add(builder.notEqual(urisSize, 0));
} else if ("owner".equals(name)) {
predicates.add(root.get(name).in(value));
} else {

View file

@ -80,23 +80,22 @@ public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
entity.setDisplayName(name);
}
@Override
public Set<String> getUris() {
return entity.getUris();
}
@Override
public void updateUris(Set<String> uri) {
entity.setUris(uri);
}
@Override
public void setName(String name) {
entity.setName(name);
}
@Override
public String getUri() {
return entity.getUri();
}
@Override
public void setUri(String uri) {
entity.setUri(uri);
}
@Override
public String getType() {
return entity.getType();

View file

@ -0,0 +1,49 @@
package org.keycloak.connections.jpa.updater.liquibase.custom;
import liquibase.exception.CustomChangeException;
import liquibase.statement.core.InsertStatement;
import liquibase.structure.core.Table;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* @author mhajas
*/
public class AuthzResourceUseMoreURIs extends CustomKeycloakTask {
@Override
protected void generateStatementsImpl() throws CustomChangeException {
try {
PreparedStatement statement = jdbcConnection.prepareStatement("select ID,URI from " + getTableName("RESOURCE_SERVER_RESOURCE"));
try {
ResultSet resultSet = statement.executeQuery();
try {
while (resultSet.next()) {
String resourceId = resultSet.getString(1);
String resourceUri = resultSet.getString(2);
InsertStatement insertComponent = new InsertStatement(null, null, database.correctObjectName("RESOURCE_URI", Table.class))
.addColumnValue("RESOURCE_ID", resourceId)
.addColumnValue("VALUE", resourceUri);
statements.add(insertComponent);
}
} finally {
resultSet.close();
}
} finally {
statement.close();
}
confirmationMessage.append("Moved " + statements.size() + " records from RESOURCE_SERVER_RESOURCE to RESOURCE_URI table");
} catch (Exception e) {
throw new CustomChangeException(getTaskId() + ": Exception when updating data from previous version", e);
}
}
@Override
protected String getTaskId() {
return "Update 4.2.0.Final-SNAPSHOT";
}
}

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ * Copyright 2017 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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.2.xsd">
<changeSet author="mhajas@redhat.com" id="authz-4.2.0.Final-SNAPSHOT">
<createTable tableName="RESOURCE_URIS">
<column name="RESOURCE_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="VALUE" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>
<addForeignKeyConstraint baseColumnNames="RESOURCE_ID" baseTableName="RESOURCE_URIS" constraintName="FK_RESOURCE_SERVER_URIS" referencedColumnNames="ID" referencedTableName="RESOURCE_SERVER_RESOURCE"/>
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.AuthzResourceUseMoreURIs"/>
<dropColumn columnName="URI" tableName="RESOURCE_SERVER_RESOURCE"/>
</changeSet>
</databaseChangeLog>

View file

@ -56,4 +56,5 @@
<include file="META-INF/jpa-changelog-4.0.0.xml"/>
<include file="META-INF/jpa-changelog-authz-4.0.0.CR1.xml"/>
<include file="META-INF/jpa-changelog-authz-4.0.0.Beta3.xml"/>
<include file="META-INF/jpa-changelog-authz-4.2.0.Final-SNAPSHOT.xml"/>
</databaseChangeLog>

View file

@ -65,18 +65,19 @@ public interface Resource {
void setDisplayName(String name);
/**
* Returns a {@link java.net.URI} that uniquely identify this resource.
* Returns a {@link List} containing all {@link java.net.URI} that uniquely identify this resource.
*
* @return an {@link java.net.URI} for this resource or null if not defined.
* @return a {@link List} if {@link java.net.URI} for this resource or empty list if not defined.
*/
String getUri();
Set<String> getUris();
/**
* Sets a {@link java.net.URI} that uniquely identify this resource.
* Sets a list of {@link java.net.URI} that uniquely identify this resource.
*
* @param uri an {@link java.net.URI} for this resource
*/
void setUri(String uri);
void updateUris(Set<String> uri);
/**
* Returns a string representing the type of this resource.

View file

@ -38,11 +38,12 @@ import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.*;
import org.keycloak.representations.idm.authorization.*;
import org.keycloak.storage.StorageId;
import org.keycloak.util.JsonSerialization;
import java.util.*;
import java.util.stream.Collectors;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -788,7 +789,7 @@ public class ModelToRepresentation {
resource.setType(model.getType());
resource.setName(model.getName());
resource.setDisplayName(model.getDisplayName());
resource.setUri(model.getUri());
resource.setUris(model.getUris());
resource.setIconUri(model.getIconUri());
resource.setOwnerManagedAccess(model.isOwnerManagedAccess());

View file

@ -2355,7 +2355,7 @@ public class RepresentationToModel {
existing.setName(resource.getName());
existing.setDisplayName(resource.getDisplayName());
existing.setType(resource.getType());
existing.setUri(resource.getUri());
existing.updateUris(resource.getUris());
existing.setIconUri(resource.getIconUri());
existing.setOwnerManagedAccess(Boolean.TRUE.equals(resource.getOwnerManagedAccess()));
existing.updateScopes(resource.getScopes().stream()
@ -2387,7 +2387,7 @@ public class RepresentationToModel {
model.setDisplayName(resource.getDisplayName());
model.setType(resource.getType());
model.setUri(resource.getUri());
model.updateUris(resource.getUris());
model.setIconUri(resource.getIconUri());
model.setOwnerManagedAccess(Boolean.TRUE.equals(resource.getOwnerManagedAccess()));

View file

@ -54,6 +54,8 @@ import org.keycloak.representations.idm.authorization.ResourceServerRepresentati
import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import java.util.Collections;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@ -221,7 +223,7 @@ public class ResourceServerService {
ResourceRepresentation defaultResource = new ResourceRepresentation();
defaultResource.setName("Default Resource");
defaultResource.setUri("/*");
defaultResource.setUris(Collections.singleton("/*"));
defaultResource.setType("urn:" + this.client.getClientId() + ":resources:default");
getResourceSetResource().create(defaultResource);

View file

@ -21,7 +21,6 @@ import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
import static org.keycloak.models.utils.RepresentationToModel.toModel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -420,22 +419,28 @@ public class ResourceSetService {
attributes.put("owner", new String[] {resourceServer.getId()});
List<Resource> serverResources = storeFactory.getResourceStore().findByResourceServer(attributes, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS);
PathMatcher<Resource> pathMatcher = new PathMatcher<Resource>() {
PathMatcher<Map.Entry<String, Resource>> pathMatcher = new PathMatcher<Map.Entry<String, Resource>>() {
@Override
protected String getPath(Resource entry) {
return entry.getUri();
protected String getPath(Map.Entry<String, Resource> entry) {
return entry.getKey();
}
@Override
protected Collection<Resource> getPaths() {
return serverResources;
protected Collection<Map.Entry<String, Resource>> getPaths() {
Map<String, Resource> result = new HashMap<>();
serverResources.forEach(resource -> resource.getUris().forEach(uri -> {
result.put(uri, resource);
}));
return result.entrySet();
}
};
Resource matches = pathMatcher.matches(uri);
Map.Entry<String, Resource> matches = pathMatcher.matches(uri);
if (matches != null) {
resources = Arrays.asList(matches);
resources = Collections.singletonList(matches.getValue());
}
}

View file

@ -37,7 +37,7 @@ public class UmaResourceRepresentation extends ResourceRepresentation {
setId(resource.getId());
setName(resource.getName());
setType(resource.getType());
setUri(resource.getUri());
setUris(resource.getUris());
setIconUri(resource.getIconUri());
setOwner(resource.getOwner());
setScopes(resource.getScopes());
@ -49,7 +49,7 @@ public class UmaResourceRepresentation extends ResourceRepresentation {
setId(resource.getId());
setName(resource.getName());
setType(resource.getType());
setUri(resource.getUri());
setUris(resource.getUris());
setIconUri(resource.getIconUri());
setOwner(resource.getOwner());
setScopes(resource.getScopes().stream().map(scope -> new ScopeRepresentation(scope.getName())).collect(Collectors.toSet()));

View file

@ -60,6 +60,10 @@
{
"name": "Resource Protected With Claim",
"uri": "/protected/context/context.jsp"
},
{
"name": "Multiple URL resource",
"uris": ["/keycloak-7269/sub-resource1/*", "/keycloak-7269/sub-resource2/{whatever-pattern}/page.jsp"]
}
],
"policies": [
@ -198,6 +202,16 @@
"applyPolicies": "[\"Request Claim Policy\"]"
}
},
{
"name": "Permission for multiple url resource",
"type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"resources": "[\"Multiple URL resource\"]",
"applyPolicies": "[\"All Users Policy\"]"
}
},
{
"name": "Request Claim Policy",
"description": "A policy that grants access based on claims from an http request",

View file

@ -0,0 +1,6 @@
<html>
<body>
<h2>sub-resource1 index1.jsp</h2>
<%@include file="../../logout-include.jsp"%>
</body>
</html>

View file

@ -0,0 +1,6 @@
<html>
<body>
<h2>sub-resource1 index2.jsp</h2>
<%@include file="../../logout-include.jsp"%>
</body>
</html>

View file

@ -0,0 +1,6 @@
<html>
<body>
<h2>sub-resource2/pattern1</h2>
<%@include file="../../../logout-include.jsp"%>
</body>
</html>

View file

@ -0,0 +1,6 @@
<html>
<body>
<h2>sub-resource2/pattern2</h2>
<%@include file="../../../logout-include.jsp"%>
</body>
</html>

View file

@ -0,0 +1,6 @@
<html>
<body>
<h2>keycloak-7269/sub-resource2/test</h2>
<%@include file="../../logout-include.jsp"%>
</body>
</html>

View file

@ -0,0 +1,6 @@
<html>
<body>
<h2>keycloak-7269/test</h2>
<%@include file="../../logout-include.jsp"%>
</body>
</html>

View file

@ -120,6 +120,10 @@
"name": "Pattern 15",
"type": "pattern-15",
"uri": "/keycloak-7148/{id}"
},
{
"name": "Pattern 16",
"uris": ["/keycloak-7269/sub-resource1", "/keycloak-7269/sub-resource2/*", "/keycloak-7269/sub-resource1/{test-pattern}/specialSuffix"]
}
],
"policies": [
@ -302,6 +306,16 @@
"default": "true",
"applyPolicies": "[\"Default Policy\"]"
}
},
{
"name": "Pattern 16 Permission",
"type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"resources": "[\"Pattern 16\"]",
"applyPolicies": "[\"Default Policy\"]"
}
}
],
"scopes": []

View file

@ -73,6 +73,18 @@
{
"name": "Pattern 15",
"path": "/keycloak-7148/{id}/*"
},
{
"name": "Pattern 16",
"path": "/keycloak-7269/sub-resource1"
},
{
"name": "Pattern 16",
"path": "/keycloak-7269/sub-resource2/*"
},
{
"name": "Pattern 16",
"path": "/keycloak-7269/sub-resource1/{test-pattern}/specialSuffix"
}
]
}

View file

@ -31,6 +31,7 @@ import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
import org.keycloak.testsuite.util.WaitUtils;
import org.openqa.selenium.By;
import javax.ws.rs.core.Response;
import java.util.Arrays;
@ -38,7 +39,7 @@ import java.util.List;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.adapter.example.authorization.AbstractBaseServletAuthzAdapterTest.RESOURCE_SERVER_ID;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -309,4 +310,60 @@ public abstract class AbstractServletAuthzAdapterTest extends AbstractBaseServle
assertTrue(hasText("Granted"));
});
}
@Test
public void testMultipleURLsForResourceRealmConfig() throws Exception {
performTests(() -> {
login("jdoe", "jdoe");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource1/index1.jsp");
waitUntilElement(By.tagName("h2")).text().contains("sub-resource1 index1.jsp");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource1/index2.jsp");
waitUntilElement(By.tagName("h2")).text().contains("sub-resource1 index2.jsp");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource2/pattern1/page.jsp");
waitUntilElement(By.tagName("h2")).text().contains("sub-resource2/pattern1");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource2/pattern2/page.jsp");
waitUntilElement(By.tagName("h2")).text().contains("sub-resource2/pattern2");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/test.jsp");
waitUntilElement(By.tagName("h2")).text().contains("keycloak-7269/test");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource2/test.jsp");
waitUntilElement(By.tagName("h2")).text().contains("keycloak-7269/sub-resource2/test");
updatePermissionPolicies("Permission for multiple url resource", "Deny Policy");
login("jdoe", "jdoe");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource1/index1.jsp");
waitUntilElement(By.tagName("h2")).text().not().contains("sub-resource1 index1.jsp");
waitUntilElement(By.tagName("h2")).text().contains("You can not access this resource.");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource1/index2.jsp");
waitUntilElement(By.tagName("h2")).text().not().contains("sub-resource1 index2.jsp");
waitUntilElement(By.tagName("h2")).text().contains("You can not access this resource.");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource2/pattern1/page.jsp");
waitUntilElement(By.tagName("h2")).text().not().contains("sub-resource2/pattern1");
waitUntilElement(By.tagName("h2")).text().contains("You can not access this resource.");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource2/pattern2/page.jsp");
waitUntilElement(By.tagName("h2")).text().not().contains("sub-resource2/pattern2");
waitUntilElement(By.tagName("h2")).text().contains("You can not access this resource.");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/test.jsp");
waitUntilElement(By.tagName("h2")).text().contains("keycloak-7269/test");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource2/test.jsp");
waitUntilElement(By.tagName("h2")).text().contains("keycloak-7269/sub-resource2/test");
updatePermissionPolicies("Permission for multiple url resource", "All Users Policy");
login("jdoe", "jdoe");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource1/index1.jsp");
waitUntilElement(By.tagName("h2")).text().contains("sub-resource1 index1.jsp");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource1/index2.jsp");
waitUntilElement(By.tagName("h2")).text().contains("sub-resource1 index2.jsp");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource2/pattern1/page.jsp");
waitUntilElement(By.tagName("h2")).text().contains("sub-resource2/pattern1");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource2/pattern2/page.jsp");
waitUntilElement(By.tagName("h2")).text().contains("sub-resource2/pattern2");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/test.jsp");
waitUntilElement(By.tagName("h2")).text().contains("keycloak-7269/test");
driver.navigate().to(getResourceServerUrl() + "/keycloak-7269/sub-resource2/test.jsp");
waitUntilElement(By.tagName("h2")).text().contains("keycloak-7269/sub-resource2/test");
});
}
}

View file

@ -451,6 +451,43 @@ public class ServletPolicyEnforcerTest extends AbstractExampleAdapterTest {
});
}
@Test
public void testMultipleUriForResourceJSONConfig() {
performTests(() -> {
login("alice", "alice");
navigateTo("/keycloak-7269/sub-resource1");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource1/whatever/specialSuffix");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource2");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource2/w/h/a/t/e/v/e/r");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 16 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/keycloak-7269/sub-resource1");
assertTrue(wasDenied());
navigateTo("/keycloak-7269/sub-resource1/whatever/specialSuffix");
assertTrue(wasDenied());
navigateTo("/keycloak-7269/sub-resource2");
assertTrue(wasDenied());
navigateTo("/keycloak-7269/sub-resource2/w/h/a/t/e/v/e/r");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 16 Permission", "Default Policy");
navigateTo("/keycloak-7269/sub-resource1");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource1/whatever/specialSuffix");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource2");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource2/w/h/a/t/e/v/e/r");
assertFalse(wasDenied());
});
}
private void navigateTo(String path) {
this.driver.navigate().to(getResourceServerUrl() + path);
}

View file

@ -28,6 +28,7 @@ import org.keycloak.testsuite.page.Form;
import org.keycloak.testsuite.util.WaitUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@ -46,8 +47,11 @@ public class ResourceForm extends Form {
@FindBy(id = "type")
private WebElement type;
@FindBy(id = "uri")
private WebElement uri;
@FindBy(id = "newUri")
private WebElement newUri;
@FindBy(xpath = "//*[@id=\"view\"]/div[1]/form/fieldset/div[5]/div/div/div/button")
private WebElement addUriButton;
@FindBy(id = "iconUri")
private WebElement iconUri;
@ -65,10 +69,24 @@ public class ResourceForm extends Form {
private ScopesInput scopesInput;
public void populate(ResourceRepresentation expected) {
while (true) {
try {
WebElement e = driver.findElement(By.xpath("//button[@data-ng-click='deleteUri($index)']"));
e.click();
} catch (NoSuchElementException e) {
break;
}
}
setInputValue(name, expected.getName());
setInputValue(displayName, expected.getDisplayName());
setInputValue(type, expected.getType());
setInputValue(uri, expected.getUri());
for (String uri : expected.getUris()) {
setInputValue(newUri, uri);
addUriButton.click();
}
setInputValue(iconUri, expected.getIconUri());
Set<ScopeRepresentation> scopes = expected.getScopes();
@ -108,7 +126,11 @@ public class ResourceForm extends Form {
representation.setName(getInputValue(name));
representation.setDisplayName(getInputValue(displayName));
representation.setType(getInputValue(type));
representation.setUri(getInputValue(uri));
for (WebElement uriInput : driver.findElements(By.xpath("//input[@ng-model='resource.uris[i]']"))) {
representation.addUri(getInputValue(uriInput));
}
representation.setIconUri(getInputValue(iconUri));
representation.setScopes(scopesInput.getSelected());

View file

@ -1201,6 +1201,7 @@ error=Error
authz-authorization=Authorization
authz-owner=Owner
authz-uri=URI
authz-uris=URIS
authz-scopes=Scopes
authz-resource=Resource
authz-resource-type=Resource Type
@ -1257,7 +1258,7 @@ authz-add-resource=Add Resource
authz-resource-name.tooltip=A unique name for this resource. The name can be used to uniquely identify a resource, useful when querying for a specific resource.
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-uri.tooltip=An URI that can also be used to uniquely identify this resource.
authz-resource-uri.tooltip=Set of URIs which are protected by 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.

View file

@ -301,6 +301,7 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
var resource = {};
resource.scopes = [];
resource.attributes = {};
resource.uris = [];
$scope.resource = angular.copy(resource);
@ -310,7 +311,17 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
}
}, true);
$scope.$watch('newUri', function() {
if ($scope.newUri && $scope.newUri.length > 0) {
$scope.changed = true;
}
}, true);
$scope.save = function() {
if ($scope.newUri && $scope.newUri.length > 0) {
$scope.addUri();
}
for (i = 0; i < $scope.resource.scopes.length; i++) {
delete $scope.resource.scopes[i].text;
}
@ -350,7 +361,17 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
}
}, true);
$scope.$watch('newUri', function() {
if ($scope.newUri && $scope.newUri.length > 0) {
$scope.changed = true;
}
}, true);
$scope.save = function() {
if ($scope.newUri && $scope.newUri.length > 0) {
$scope.addUri();
}
for (i = 0; i < $scope.resource.scopes.length; i++) {
delete $scope.resource.scopes[i].text;
}
@ -412,6 +433,15 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
$scope.removeAttribute = function(key) {
delete $scope.resource.attributes[key];
}
$scope.addUri = function() {
$scope.resource.uris.push($scope.newUri);
$scope.newUri = "";
}
$scope.deleteUri = function(index) {
$scope.resource.uris.splice(index, 1);
}
});
var Scopes = {

View file

@ -44,9 +44,21 @@
<kc-tooltip>{{:: 'authz-resource-type.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="uri">{{:: 'authz-uri' | translate}} </label>
<label class="col-md-2 control-label" for="newUri">{{:: 'authz-uri' | translate}} </label>
<div class="col-sm-6">
<input class="form-control" type="text" id="uri" name="name" data-ng-model="resource.uri" autofocus>
<div class="input-group" ng-repeat="(i, uri) in resource.uris track by $index">
<input class="form-control" ng-model="resource.uris[i]">
<div class="input-group-btn">
<button class="btn btn-default" type="button" data-ng-click="deleteUri($index)"><span class="fa fa-minus"></span></button>
</div>
</div>
<div class="input-group">
<input class="form-control" ng-model="newUri" id="newUri">
<div class="input-group-btn">
<button class="btn btn-default" type="button" data-ng-click="newUri.length > 0 && addUri()"><span class="fa fa-plus"></span></button>
</div>
</div>
</div>
<kc-tooltip>{{:: 'authz-resource-uri.tooltip' | translate}}</kc-tooltip>
</div>

View file

@ -72,7 +72,7 @@
<th width="1%"></th>
<th>{{:: 'name' | translate}}</th>
<th>{{:: 'type' | translate}}</th>
<th>{{:: 'authz-uri' | translate}}</th>
<th>{{:: 'authz-uris' | translate}}</th>
<th>{{:: 'authz-owner' | translate}}</th>
<th width="11%" style="text-align: center">{{:: 'actions' | translate}}</th>
</tr>
@ -108,8 +108,9 @@
<span data-ng-show="!resource.type">{{:: 'authz-no-type-defined' | translate}}</span>
</td>
<td>
<span data-ng-show="resource.uri">{{resource.uri}}</span>
<span data-ng-show="!resource.uri">{{:: 'authz-no-uri-defined' | translate}}</span>
<span data-ng-show="resource.uris.length == 0">{{:: 'authz-no-uri-defined' | translate}}</span>
<span data-ng-show="resource.uris.length == 1">{{resource.uris[0]}}</span>
<span data-ng-show="resource.uris.length > 1">{{resource.uris.length}} {{:: 'authz-uris' | translate}}</span>
</td>
<td>{{resource.owner.name}}</td>
<td align="center">
@ -144,6 +145,11 @@
<span data-ng-show="resource.policies && !resource.policies.length">{{:: 'authz-no-permission-assigned' | translate}}</span>
<span ng-repeat="policy in resource.policies" data-ng-show="resource.policies.length > 0"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policy.type}}/{{policy.id}}">{{policy.name}}</a>{{$last ? '' : ', '}}</span>
</dd>
<dt>{{:: 'authz-uris' | translate}}</dt>
<dd>
<span data-ng-show="resource.uris && !resource.uris.length">{{:: 'authz-no-uri-defined' | translate}}</span>
<span ng-repeat="uri in resource.uris" data-ng-show="resource.uris.length > 0">{{uri}}{{$last ? '' : ', '}}</span>
</dd>
</dl>
</div>
</div>