KEYCLOAK-7269 Setting more uris for Authorization Resource
This commit is contained in:
parent
fba2bf0b2f
commit
5aebc74f8c
37 changed files with 523 additions and 124 deletions
|
@ -225,9 +225,10 @@ public class PolicyEnforcer {
|
|||
for (String id : protectedResource.findAll()) {
|
||||
ResourceRepresentation resourceDescription = protectedResource.findById(id);
|
||||
|
||||
if (resourceDescription.getUri() != null) {
|
||||
PathConfig pathConfig = PathConfig.createPathConfig(resourceDescription);
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
if (resourceDescription.getUris() != null && !resourceDescription.getUris().isEmpty()) {
|
||||
for(PathConfig pathConfig : PathConfig.createPathConfigs(resourceDescription)) {
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
|
|
@ -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,30 +119,35 @@ public class PolicyEnforcerConfig {
|
|||
|
||||
public static class PathConfig {
|
||||
|
||||
public static PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
|
||||
PathConfig pathConfig = new PathConfig();
|
||||
public static Set<PathConfig> createPathConfigs(ResourceRepresentation resourceDescription) {
|
||||
Set<PathConfig> pathConfigs = new HashSet<>();
|
||||
|
||||
pathConfig.setId(resourceDescription.getId());
|
||||
pathConfig.setName(resourceDescription.getName());
|
||||
for (String uri : resourceDescription.getUris()) {
|
||||
|
||||
String uri = resourceDescription.getUri();
|
||||
PathConfig pathConfig = new PathConfig();
|
||||
|
||||
if (uri == null || "".equals(uri.trim())) {
|
||||
throw new RuntimeException("Failed to configure paths. Resource [" + resourceDescription.getName() + "] has an invalid or empty URI [" + uri + "].");
|
||||
pathConfig.setId(resourceDescription.getId());
|
||||
pathConfig.setName(resourceDescription.getName());
|
||||
|
||||
if (uri == null || "".equals(uri.trim())) {
|
||||
throw new RuntimeException("Failed to configure paths. Resource [" + resourceDescription.getName() + "] has an invalid or empty URI [" + uri + "].");
|
||||
}
|
||||
|
||||
pathConfig.setPath(uri);
|
||||
|
||||
List<String> scopeNames = new ArrayList<>();
|
||||
|
||||
for (ScopeRepresentation scope : resourceDescription.getScopes()) {
|
||||
scopeNames.add(scope.getName());
|
||||
}
|
||||
|
||||
pathConfig.setScopes(scopeNames);
|
||||
pathConfig.setType(resourceDescription.getType());
|
||||
|
||||
pathConfigs.add(pathConfig);
|
||||
}
|
||||
|
||||
pathConfig.setPath(uri);
|
||||
|
||||
List<String> scopeNames = new ArrayList<>();
|
||||
|
||||
for (ScopeRepresentation scope : resourceDescription.getScopes()) {
|
||||
scopeNames.add(scope.getName());
|
||||
}
|
||||
|
||||
pathConfig.setScopes(scopeNames);
|
||||
pathConfig.setType(resourceDescription.getType());
|
||||
|
||||
return pathConfig;
|
||||
return pathConfigs;
|
||||
}
|
||||
|
||||
private String name;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,8 +89,10 @@ public class StoreFactoryCacheManager extends CacheManager {
|
|||
addInvalidations(InResourcePredicate.create().resource(type), invalidations);
|
||||
}
|
||||
|
||||
if (uri != null) {
|
||||
invalidations.add(StoreFactoryCacheSession.getResourceByUriCacheKey(uri, serverId));
|
||||
if (uris != null) {
|
||||
for (String uri: uris) {
|
||||
invalidations.add(StoreFactoryCacheSession.getResourceByUriCacheKey(uri, serverId));
|
||||
}
|
||||
}
|
||||
|
||||
if (scopes != null) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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()));
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<h2>sub-resource1 index1.jsp</h2>
|
||||
<%@include file="../../logout-include.jsp"%>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<h2>sub-resource1 index2.jsp</h2>
|
||||
<%@include file="../../logout-include.jsp"%>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<h2>sub-resource2/pattern1</h2>
|
||||
<%@include file="../../../logout-include.jsp"%>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<h2>sub-resource2/pattern2</h2>
|
||||
<%@include file="../../../logout-include.jsp"%>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<h2>keycloak-7269/sub-resource2/test</h2>
|
||||
<%@include file="../../logout-include.jsp"%>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<h2>keycloak-7269/test</h2>
|
||||
<%@include file="../../logout-include.jsp"%>
|
||||
</body>
|
||||
</html>
|
|
@ -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": []
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue