Merge pull request #5211 from pedroigor/KEYCLOAK-7367
[KEYCLOAK-7367] - User-Managed Policy Provider
This commit is contained in:
commit
f8919f8baa
48 changed files with 1125 additions and 176 deletions
|
@ -18,9 +18,12 @@ package org.keycloak.authorization.policy.provider.group;
|
||||||
|
|
||||||
import static org.keycloak.models.utils.ModelToRepresentation.buildGroupPath;
|
import static org.keycloak.models.utils.ModelToRepresentation.buildGroupPath;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.keycloak.authorization.AuthorizationProvider;
|
||||||
import org.keycloak.authorization.attribute.Attributes;
|
import org.keycloak.authorization.attribute.Attributes;
|
||||||
|
import org.keycloak.authorization.attribute.Attributes.Entry;
|
||||||
import org.keycloak.authorization.model.Policy;
|
import org.keycloak.authorization.model.Policy;
|
||||||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
||||||
import org.keycloak.authorization.policy.provider.PolicyProvider;
|
import org.keycloak.authorization.policy.provider.PolicyProvider;
|
||||||
|
@ -42,11 +45,13 @@ public class GroupPolicyProvider implements PolicyProvider {
|
||||||
@Override
|
@Override
|
||||||
public void evaluate(Evaluation evaluation) {
|
public void evaluate(Evaluation evaluation) {
|
||||||
GroupPolicyRepresentation policy = representationFunction.apply(evaluation.getPolicy());
|
GroupPolicyRepresentation policy = representationFunction.apply(evaluation.getPolicy());
|
||||||
RealmModel realm = evaluation.getAuthorizationProvider().getRealm();
|
AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider();
|
||||||
|
RealmModel realm = authorizationProvider.getRealm();
|
||||||
Attributes.Entry groupsClaim = evaluation.getContext().getIdentity().getAttributes().getValue(policy.getGroupsClaim());
|
Attributes.Entry groupsClaim = evaluation.getContext().getIdentity().getAttributes().getValue(policy.getGroupsClaim());
|
||||||
|
|
||||||
if (groupsClaim == null || groupsClaim.isEmpty()) {
|
if (groupsClaim == null || groupsClaim.isEmpty()) {
|
||||||
return;
|
List<String> userGroups = evaluation.getRealm().getUserGroups(evaluation.getContext().getIdentity().getId());
|
||||||
|
groupsClaim = new Entry(policy.getGroupsClaim(), userGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (GroupPolicyRepresentation.GroupDefinition definition : policy.getGroups()) {
|
for (GroupPolicyRepresentation.GroupDefinition definition : policy.getGroups()) {
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.authorization.policy.provider.permission;
|
||||||
|
|
||||||
|
import org.keycloak.authorization.AuthorizationProvider;
|
||||||
|
import org.keycloak.authorization.model.Policy;
|
||||||
|
import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
|
||||||
|
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
||||||
|
import org.keycloak.authorization.policy.provider.PolicyProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public class AbstractPermissionProvider implements PolicyProvider {
|
||||||
|
|
||||||
|
public AbstractPermissionProvider() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void evaluate(Evaluation evaluation) {
|
||||||
|
if (!(evaluation instanceof DefaultEvaluation)) {
|
||||||
|
throw new IllegalArgumentException("Unexpected evaluation instance type [" + evaluation.getClass() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
Policy policy = evaluation.getPolicy();
|
||||||
|
AuthorizationProvider authorization = evaluation.getAuthorizationProvider();
|
||||||
|
|
||||||
|
policy.getAssociatedPolicies().forEach(associatedPolicy -> {
|
||||||
|
PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType());
|
||||||
|
DefaultEvaluation.class.cast(evaluation).setPolicy(associatedPolicy);
|
||||||
|
policyProvider.evaluate(evaluation);
|
||||||
|
evaluation.denyIfNoEffect();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* JBoss, Home of Professional Open Source
|
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||||
*
|
* and other contributors as indicated by the @author tags.
|
||||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -15,26 +14,11 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.keycloak.authorization.policy.provider.resource;
|
package org.keycloak.authorization.policy.provider.permission;
|
||||||
|
|
||||||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
|
||||||
import org.keycloak.authorization.policy.provider.PolicyProvider;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
*/
|
*/
|
||||||
public class ResourcePolicyProvider implements PolicyProvider {
|
public class ResourcePolicyProvider extends AbstractPermissionProvider {
|
||||||
|
|
||||||
public ResourcePolicyProvider() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void evaluate(Evaluation evaluation) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,4 +1,20 @@
|
||||||
package org.keycloak.authorization.policy.provider.resource;
|
/*
|
||||||
|
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.authorization.policy.provider.permission;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
|
@ -1,7 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* JBoss, Home of Professional Open Source
|
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||||
*
|
* and other contributors as indicated by the @author tags.
|
||||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -15,22 +14,11 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.keycloak.authorization.policy.provider.scope;
|
package org.keycloak.authorization.policy.provider.permission;
|
||||||
|
|
||||||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
|
||||||
import org.keycloak.authorization.policy.provider.PolicyProvider;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
*/
|
*/
|
||||||
public class ScopePolicyProvider implements PolicyProvider {
|
public class ScopePolicyProvider extends AbstractPermissionProvider {
|
||||||
|
|
||||||
@Override
|
|
||||||
public void evaluate(Evaluation evaluation) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,4 +1,20 @@
|
||||||
package org.keycloak.authorization.policy.provider.scope;
|
/*
|
||||||
|
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.authorization.policy.provider.permission;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authorization.AuthorizationProvider;
|
import org.keycloak.authorization.AuthorizationProvider;
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.authorization.policy.provider.permission;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public class UMAPolicyProvider extends AbstractPermissionProvider {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.authorization.policy.provider.permission;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.authorization.AuthorizationProvider;
|
||||||
|
import org.keycloak.authorization.model.Policy;
|
||||||
|
import org.keycloak.authorization.policy.provider.PolicyProvider;
|
||||||
|
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public class UMAPolicyProviderFactory implements PolicyProviderFactory<PolicyRepresentation> {
|
||||||
|
|
||||||
|
private UMAPolicyProvider provider = new UMAPolicyProvider();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "UMA";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getGroup() {
|
||||||
|
return "Others";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyProvider create(AuthorizationProvider authorization) {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyProvider create(KeycloakSession session) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
|
||||||
|
verifyCircularReference(policy, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
|
||||||
|
verifyCircularReference(policy, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
|
||||||
|
verifyCircularReference(policy, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyRepresentation toRepresentation(Policy policy) {
|
||||||
|
return new PolicyRepresentation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<PolicyRepresentation> getRepresentationType() {
|
||||||
|
return PolicyRepresentation.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyCircularReference(Policy policy, List<String> ids) {
|
||||||
|
if (!policy.getType().equals("uma")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ids.contains(policy.getId())) {
|
||||||
|
throw new RuntimeException("Circular reference found [" + policy.getName() + "].");
|
||||||
|
}
|
||||||
|
|
||||||
|
ids.add(policy.getId());
|
||||||
|
|
||||||
|
for (Policy associated : policy.getAssociatedPolicies()) {
|
||||||
|
verifyCircularReference(associated, ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRemove(Policy policy, AuthorizationProvider authorization) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "uma";
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,10 +36,11 @@
|
||||||
|
|
||||||
org.keycloak.authorization.policy.provider.aggregated.AggregatePolicyProviderFactory
|
org.keycloak.authorization.policy.provider.aggregated.AggregatePolicyProviderFactory
|
||||||
org.keycloak.authorization.policy.provider.js.JSPolicyProviderFactory
|
org.keycloak.authorization.policy.provider.js.JSPolicyProviderFactory
|
||||||
org.keycloak.authorization.policy.provider.resource.ResourcePolicyProviderFactory
|
org.keycloak.authorization.policy.provider.permission.ResourcePolicyProviderFactory
|
||||||
org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory
|
org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory
|
||||||
org.keycloak.authorization.policy.provider.scope.ScopePolicyProviderFactory
|
org.keycloak.authorization.policy.provider.permission.ScopePolicyProviderFactory
|
||||||
org.keycloak.authorization.policy.provider.time.TimePolicyProviderFactory
|
org.keycloak.authorization.policy.provider.time.TimePolicyProviderFactory
|
||||||
org.keycloak.authorization.policy.provider.user.UserPolicyProviderFactory
|
org.keycloak.authorization.policy.provider.user.UserPolicyProviderFactory
|
||||||
org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory
|
org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory
|
||||||
org.keycloak.authorization.policy.provider.group.GroupPolicyProviderFactory
|
org.keycloak.authorization.policy.provider.group.GroupPolicyProviderFactory
|
||||||
|
org.keycloak.authorization.policy.provider.permission.UMAPolicyProviderFactory
|
|
@ -18,6 +18,7 @@ package org.keycloak.models.cache.infinispan.authorization;
|
||||||
|
|
||||||
import org.keycloak.authorization.model.CachedModel;
|
import org.keycloak.authorization.model.CachedModel;
|
||||||
import org.keycloak.authorization.model.PermissionTicket;
|
import org.keycloak.authorization.model.PermissionTicket;
|
||||||
|
import org.keycloak.authorization.model.Policy;
|
||||||
import org.keycloak.authorization.model.Resource;
|
import org.keycloak.authorization.model.Resource;
|
||||||
import org.keycloak.authorization.model.ResourceServer;
|
import org.keycloak.authorization.model.ResourceServer;
|
||||||
import org.keycloak.authorization.model.Scope;
|
import org.keycloak.authorization.model.Scope;
|
||||||
|
@ -41,7 +42,7 @@ public class PermissionTicketAdapter implements PermissionTicket, CachedModel<Pe
|
||||||
@Override
|
@Override
|
||||||
public PermissionTicket getDelegateForUpdate() {
|
public PermissionTicket getDelegateForUpdate() {
|
||||||
if (updated == null) {
|
if (updated == null) {
|
||||||
cacheSession.registerPermissionTicketInvalidation(cached.getId(), cached.getOwner(), cached.getResourceId(), cached.getScopeId(), cached.getResourceServerId());
|
cacheSession.registerPermissionTicketInvalidation(cached.getId(), cached.getOwner(), cached.getRequester(), cached.getResourceId(), cached.getScopeId(), cached.getResourceServerId());
|
||||||
updated = cacheSession.getPermissionTicketStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
|
updated = cacheSession.getPermissionTicketStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
|
||||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||||
}
|
}
|
||||||
|
@ -113,7 +114,7 @@ public class PermissionTicketAdapter implements PermissionTicket, CachedModel<Pe
|
||||||
@Override
|
@Override
|
||||||
public void setGrantedTimestamp(Long millis) {
|
public void setGrantedTimestamp(Long millis) {
|
||||||
getDelegateForUpdate();
|
getDelegateForUpdate();
|
||||||
cacheSession.registerPermissionTicketInvalidation(cached.getId(), cached.getOwner(), cached.getResourceId(), cached.getScopeId(), cached.getResourceServerId());
|
cacheSession.registerPermissionTicketInvalidation(cached.getId(), cached.getOwner(), cached.getRequester(), cached.getResourceId(), cached.getScopeId(), cached.getResourceServerId());
|
||||||
updated.setGrantedTimestamp(millis);
|
updated.setGrantedTimestamp(millis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +123,19 @@ public class PermissionTicketAdapter implements PermissionTicket, CachedModel<Pe
|
||||||
return cacheSession.getResourceServerStore().findById(cached.getResourceServerId());
|
return cacheSession.getResourceServerStore().findById(cached.getResourceServerId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Policy getPolicy() {
|
||||||
|
if (isUpdated()) return updated.getPolicy();
|
||||||
|
return cacheSession.getPolicyStore().findById(cached.getPolicy(), cached.getResourceServerId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPolicy(Policy policy) {
|
||||||
|
getDelegateForUpdate();
|
||||||
|
cacheSession.registerPermissionTicketInvalidation(cached.getId(), cached.getOwner(), cached.getRequester(), cached.getResourceId(), cached.getScopeId(), cached.getResourceServerId());
|
||||||
|
updated.setPolicy(policy);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Resource getResource() {
|
public Resource getResource() {
|
||||||
return cacheSession.getResourceStore().findById(cached.getResourceId(), getResourceServer().getId());
|
return cacheSession.getResourceStore().findById(cached.getResourceId(), getResourceServer().getId());
|
||||||
|
|
|
@ -100,7 +100,6 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||||
getDelegateForUpdate();
|
getDelegateForUpdate();
|
||||||
cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(), cached.getScopesIds(), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId());
|
cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(), cached.getScopesIds(), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId());
|
||||||
updated.setName(name);
|
updated.setName(name);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -278,6 +277,19 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
||||||
return scopes;
|
return scopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOwner() {
|
||||||
|
if (isUpdated()) return updated.getOwner();
|
||||||
|
return cached.getOwner();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOwner(String owner) {
|
||||||
|
getDelegateForUpdate();
|
||||||
|
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getScopesIds(), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId());
|
||||||
|
updated.setOwner(owner);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -199,7 +199,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||||
|
|
||||||
for (Scope scope : updated.getScopes()) {
|
for (Scope scope : updated.getScopes()) {
|
||||||
if (!scopes.contains(scope)) {
|
if (!scopes.contains(scope)) {
|
||||||
PermissionTicketStore permissionStore = cacheSession.getPermissionTicketStoreDelegate();
|
PermissionTicketStore permissionStore = cacheSession.getPermissionTicketStore();
|
||||||
List<PermissionTicket> permissions = permissionStore.findByScope(scope.getId(), getResourceServer().getId());
|
List<PermissionTicket> permissions = permissionStore.findByScope(scope.getId(), getResourceServer().getId());
|
||||||
|
|
||||||
for (PermissionTicket permission : permissions) {
|
for (PermissionTicket permission : permissions) {
|
||||||
|
|
|
@ -135,10 +135,11 @@ public class StoreFactoryCacheManager extends CacheManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void permissionTicketUpdated(String id, String owner, String resource, String scope, String serverId, Set<String> invalidations) {
|
public void permissionTicketUpdated(String id, String owner, String requester, String resource, String scope, String serverId, Set<String> invalidations) {
|
||||||
invalidations.add(id);
|
invalidations.add(id);
|
||||||
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByOwner(owner, serverId));
|
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByOwner(owner, serverId));
|
||||||
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByResource(resource, serverId));
|
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByResource(resource, serverId));
|
||||||
|
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByGranted(requester, serverId));
|
||||||
if (scope != null) {
|
if (scope != null) {
|
||||||
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByScope(scope, serverId));
|
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByScope(scope, serverId));
|
||||||
}
|
}
|
||||||
|
@ -148,8 +149,8 @@ public class StoreFactoryCacheManager extends CacheManager {
|
||||||
policyUpdated(id, name, resources, resourceTypes, scopes, serverId, invalidations);
|
policyUpdated(id, name, resources, resourceTypes, scopes, serverId, invalidations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void permissionTicketRemoval(String id, String owner, String resource, String scope, String serverId, Set<String> invalidations) {
|
public void permissionTicketRemoval(String id, String owner, String requester, String resource, String scope, String serverId, Set<String> invalidations) {
|
||||||
permissionTicketUpdated(id, owner, resource, scope, serverId, invalidations);
|
permissionTicketUpdated(id, owner, requester, resource, scope, serverId, invalidations);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.authorization.UserManagedPermissionUtil;
|
||||||
import org.keycloak.authorization.model.PermissionTicket;
|
import org.keycloak.authorization.model.PermissionTicket;
|
||||||
import org.keycloak.authorization.model.Policy;
|
import org.keycloak.authorization.model.Policy;
|
||||||
import org.keycloak.authorization.model.Resource;
|
import org.keycloak.authorization.model.Resource;
|
||||||
|
@ -283,12 +284,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
||||||
invalidationEvents.add(PolicyUpdatedEvent.create(id, name, resources, resourceTypes, scopes, serverId));
|
invalidationEvents.add(PolicyUpdatedEvent.create(id, name, resources, resourceTypes, scopes, serverId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerPermissionTicketInvalidation(String id, String owner, String resource, String scope, String serverId) {
|
public void registerPermissionTicketInvalidation(String id, String owner, String requester, String resource, String scope, String serverId) {
|
||||||
cache.permissionTicketUpdated(id, owner, resource, scope, serverId, invalidations);
|
cache.permissionTicketUpdated(id, owner, requester, resource, scope, serverId, invalidations);
|
||||||
PermissionTicketAdapter adapter = managedPermissionTickets.get(id);
|
PermissionTicketAdapter adapter = managedPermissionTickets.get(id);
|
||||||
if (adapter != null) adapter.invalidateFlag();
|
if (adapter != null) adapter.invalidateFlag();
|
||||||
|
|
||||||
invalidationEvents.add(PermissionTicketUpdatedEvent.create(id, owner, resource, scope, serverId));
|
invalidationEvents.add(PermissionTicketUpdatedEvent.create(id, owner, requester, resource, scope, serverId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<String> getResourceTypes(Set<String> resources, String serverId) {
|
private Set<String> getResourceTypes(Set<String> resources, String serverId) {
|
||||||
|
@ -384,6 +385,10 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
||||||
return "permission.ticket.scope." + scopeId + "." + serverId;
|
return "permission.ticket.scope." + scopeId + "." + serverId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getPermissionTicketByGranted(String userId, String serverId) {
|
||||||
|
return "permission.ticket.granted." + userId + "." + serverId;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getPermissionTicketByOwner(String owner, String serverId) {
|
public static String getPermissionTicketByOwner(String owner, String serverId) {
|
||||||
return "permission.ticket.owner." + owner + "." + serverId;
|
return "permission.ticket.owner." + owner + "." + serverId;
|
||||||
}
|
}
|
||||||
|
@ -836,7 +841,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
||||||
@Override
|
@Override
|
||||||
public PermissionTicket create(String resourceId, String scopeId, String requester, ResourceServer resourceServer) {
|
public PermissionTicket create(String resourceId, String scopeId, String requester, ResourceServer resourceServer) {
|
||||||
PermissionTicket created = getPermissionTicketStoreDelegate().create(resourceId, scopeId, requester, resourceServer);
|
PermissionTicket created = getPermissionTicketStoreDelegate().create(resourceId, scopeId, requester, resourceServer);
|
||||||
registerPermissionTicketInvalidation(created.getId(), created.getOwner(), created.getResource().getId(), scopeId, created.getResourceServer().getId());
|
registerPermissionTicketInvalidation(created.getId(), created.getOwner(), created.getRequester(), created.getResource().getId(), scopeId, created.getResourceServer().getId());
|
||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -851,9 +856,10 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
||||||
if (permission.getScope() != null) {
|
if (permission.getScope() != null) {
|
||||||
scopeId = permission.getScope().getId();
|
scopeId = permission.getScope().getId();
|
||||||
}
|
}
|
||||||
invalidationEvents.add(PermissionTicketRemovedEvent.create(id, permission.getOwner(), permission.getResource().getId(), scopeId, permission.getResourceServer().getId()));
|
invalidationEvents.add(PermissionTicketRemovedEvent.create(id, permission.getOwner(), permission.getRequester(), permission.getResource().getId(), scopeId, permission.getResourceServer().getId()));
|
||||||
cache.permissionTicketRemoval(id, permission.getOwner(), permission.getResource().getId(), scopeId, permission.getResourceServer().getId(), invalidations);
|
cache.permissionTicketRemoval(id, permission.getOwner(), permission.getRequester(), permission.getResource().getId(), scopeId, permission.getResourceServer().getId(), invalidations);
|
||||||
getPermissionTicketStoreDelegate().delete(id);
|
getPermissionTicketStoreDelegate().delete(id);
|
||||||
|
UserManagedPermissionUtil.removePolicy(permission, StoreFactoryCacheSession.this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -908,6 +914,13 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
||||||
return getPermissionTicketStoreDelegate().find(attributes, resourceServerId, firstResult, maxResult);
|
return getPermissionTicketStoreDelegate().find(attributes, resourceServerId, firstResult, maxResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PermissionTicket> findGranted(String userId, String resourceServerId) {
|
||||||
|
String cacheKey = getPermissionTicketByGranted(userId, resourceServerId);
|
||||||
|
return cacheQuery(cacheKey, PermissionTicketListQuery.class, () -> getPermissionTicketStoreDelegate().findGranted(userId, resourceServerId),
|
||||||
|
(revision, permissions) -> new PermissionTicketListQuery(revision, cacheKey, permissions.stream().map(permission -> permission.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PermissionTicket> findByOwner(String owner, String resourceServerId) {
|
public List<PermissionTicket> findByOwner(String owner, String resourceServerId) {
|
||||||
String cacheKey = getPermissionTicketByOwner(owner, resourceServerId);
|
String cacheKey = getPermissionTicketByOwner(owner, resourceServerId);
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.models.cache.infinispan.authorization.entities;
|
package org.keycloak.models.cache.infinispan.authorization.entities;
|
||||||
|
|
||||||
import org.keycloak.authorization.model.PermissionTicket;
|
import org.keycloak.authorization.model.PermissionTicket;
|
||||||
|
import org.keycloak.authorization.model.Policy;
|
||||||
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
|
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,6 +34,7 @@ public class CachedPermissionTicket extends AbstractRevisioned implements InReso
|
||||||
private boolean granted;
|
private boolean granted;
|
||||||
private Long createdTimestamp;
|
private Long createdTimestamp;
|
||||||
private Long grantedTimestamp;
|
private Long grantedTimestamp;
|
||||||
|
private String policy;
|
||||||
|
|
||||||
public CachedPermissionTicket(Long revision, PermissionTicket permissionTicket) {
|
public CachedPermissionTicket(Long revision, PermissionTicket permissionTicket) {
|
||||||
super(revision, permissionTicket.getId());
|
super(revision, permissionTicket.getId());
|
||||||
|
@ -46,6 +48,10 @@ public class CachedPermissionTicket extends AbstractRevisioned implements InReso
|
||||||
this.granted = permissionTicket.isGranted();
|
this.granted = permissionTicket.isGranted();
|
||||||
createdTimestamp = permissionTicket.getCreatedTimestamp();
|
createdTimestamp = permissionTicket.getCreatedTimestamp();
|
||||||
grantedTimestamp = permissionTicket.getGrantedTimestamp();
|
grantedTimestamp = permissionTicket.getGrantedTimestamp();
|
||||||
|
Policy policy = permissionTicket.getPolicy();
|
||||||
|
if (policy != null) {
|
||||||
|
this.policy = policy.getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOwner() {
|
public String getOwner() {
|
||||||
|
@ -80,4 +86,7 @@ public class CachedPermissionTicket extends AbstractRevisioned implements InReso
|
||||||
return this.resourceServerId;
|
return this.resourceServerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPolicy() {
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
|
||||||
private Set<String> associatedPoliciesIds;
|
private Set<String> associatedPoliciesIds;
|
||||||
private Set<String> resourcesIds;
|
private Set<String> resourcesIds;
|
||||||
private Set<String> scopesIds;
|
private Set<String> scopesIds;
|
||||||
|
private final String owner;
|
||||||
|
|
||||||
public CachedPolicy(Long revision, Policy policy) {
|
public CachedPolicy(Long revision, Policy policy) {
|
||||||
super(revision, policy.getId());
|
super(revision, policy.getId());
|
||||||
|
@ -58,6 +59,7 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
|
||||||
this.associatedPoliciesIds = policy.getAssociatedPolicies().stream().map(Policy::getId).collect(Collectors.toSet());
|
this.associatedPoliciesIds = policy.getAssociatedPolicies().stream().map(Policy::getId).collect(Collectors.toSet());
|
||||||
this.resourcesIds = policy.getResources().stream().map(Resource::getId).collect(Collectors.toSet());
|
this.resourcesIds = policy.getResources().stream().map(Resource::getId).collect(Collectors.toSet());
|
||||||
this.scopesIds = policy.getScopes().stream().map(Scope::getId).collect(Collectors.toSet());
|
this.scopesIds = policy.getScopes().stream().map(Scope::getId).collect(Collectors.toSet());
|
||||||
|
this.owner = policy.getOwner();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getType() {
|
public String getType() {
|
||||||
|
@ -100,4 +102,7 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
|
||||||
return this.resourceServerId;
|
return this.resourceServerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getOwner() {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,11 +32,13 @@ public class PermissionTicketRemovedEvent extends InvalidationEvent implements A
|
||||||
private String resource;
|
private String resource;
|
||||||
private String scope;
|
private String scope;
|
||||||
private String serverId;
|
private String serverId;
|
||||||
|
private String requester;
|
||||||
|
|
||||||
public static PermissionTicketRemovedEvent create(String id, String owner, String resource, String scope, String serverId) {
|
public static PermissionTicketRemovedEvent create(String id, String owner, String requester, String resource, String scope, String serverId) {
|
||||||
PermissionTicketRemovedEvent event = new PermissionTicketRemovedEvent();
|
PermissionTicketRemovedEvent event = new PermissionTicketRemovedEvent();
|
||||||
event.id = id;
|
event.id = id;
|
||||||
event.owner = owner;
|
event.owner = owner;
|
||||||
|
event.requester = requester;
|
||||||
event.resource = resource;
|
event.resource = resource;
|
||||||
event.scope = scope;
|
event.scope = scope;
|
||||||
event.serverId = serverId;
|
event.serverId = serverId;
|
||||||
|
@ -55,6 +57,6 @@ public class PermissionTicketRemovedEvent extends InvalidationEvent implements A
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
|
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
|
||||||
cache.permissionTicketRemoval(id, owner, resource, scope, serverId, invalidations);
|
cache.permissionTicketRemoval(id, owner, requester, resource, scope, serverId, invalidations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,11 +32,13 @@ public class PermissionTicketUpdatedEvent extends InvalidationEvent implements A
|
||||||
private String resource;
|
private String resource;
|
||||||
private String scope;
|
private String scope;
|
||||||
private String serverId;
|
private String serverId;
|
||||||
|
private String requester;
|
||||||
|
|
||||||
public static PermissionTicketUpdatedEvent create(String id, String owner, String resource, String scope, String serverId) {
|
public static PermissionTicketUpdatedEvent create(String id, String owner, String requester, String resource, String scope, String serverId) {
|
||||||
PermissionTicketUpdatedEvent event = new PermissionTicketUpdatedEvent();
|
PermissionTicketUpdatedEvent event = new PermissionTicketUpdatedEvent();
|
||||||
event.id = id;
|
event.id = id;
|
||||||
event.owner = owner;
|
event.owner = owner;
|
||||||
|
event.requester = requester;
|
||||||
event.resource = resource;
|
event.resource = resource;
|
||||||
event.scope = scope;
|
event.scope = scope;
|
||||||
event.serverId = serverId;
|
event.serverId = serverId;
|
||||||
|
@ -55,6 +57,6 @@ public class PermissionTicketUpdatedEvent extends InvalidationEvent implements A
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
|
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
|
||||||
cache.permissionTicketUpdated(id, owner, resource, scope, serverId, invalidations);
|
cache.permissionTicketUpdated(id, owner, requester, resource, scope, serverId, invalidations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,10 @@ public class PermissionTicketEntity {
|
||||||
@JoinColumn(name = "RESOURCE_SERVER_ID")
|
@JoinColumn(name = "RESOURCE_SERVER_ID")
|
||||||
private ResourceServerEntity resourceServer;
|
private ResourceServerEntity resourceServer;
|
||||||
|
|
||||||
|
@ManyToOne(optional = true, fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "POLICY_ID")
|
||||||
|
private PolicyEntity policy;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
@ -144,6 +148,14 @@ public class PermissionTicketEntity {
|
||||||
return grantedTimestamp != null;
|
return grantedTimestamp != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PolicyEntity getPolicy() {
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPolicy(PolicyEntity policy) {
|
||||||
|
this.policy = policy;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -113,6 +113,9 @@ public class PolicyEntity {
|
||||||
@JoinTable(name = "SCOPE_POLICY", joinColumns = @JoinColumn(name = "POLICY_ID"), inverseJoinColumns = @JoinColumn(name = "SCOPE_ID"))
|
@JoinTable(name = "SCOPE_POLICY", joinColumns = @JoinColumn(name = "POLICY_ID"), inverseJoinColumns = @JoinColumn(name = "SCOPE_ID"))
|
||||||
private Set<ScopeEntity> scopes = new HashSet<>();
|
private Set<ScopeEntity> scopes = new HashSet<>();
|
||||||
|
|
||||||
|
@Column(name = "OWNER")
|
||||||
|
private String owner;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
@ -201,6 +204,14 @@ public class PolicyEntity {
|
||||||
this.associatedPolicies = associatedPolicies;
|
this.associatedPolicies = associatedPolicies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getOwner() {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOwner(String owner) {
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -56,7 +56,7 @@ import org.hibernate.annotations.FetchMode;
|
||||||
@NamedQuery(name="findAnyResourceIdByOwner", query="select r.id from ResourceEntity r where 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 r.uri = :uri"),
|
||||||
@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="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.type = :type"),
|
@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 "),
|
@NamedQuery(name="findResourceIdByServerId", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId "),
|
||||||
@NamedQuery(name="findResourceIdByScope", query="select r.id from ResourceEntity r inner join r.scopes s where r.resourceServer.id = :serverId and (s.resourceServer.id = :serverId and s.id in (:scopeIds))"),
|
@NamedQuery(name="findResourceIdByScope", query="select r.id from ResourceEntity r inner join r.scopes s where r.resourceServer.id = :serverId and (s.resourceServer.id = :serverId and s.id in (:scopeIds))"),
|
||||||
@NamedQuery(name="deleteResourceByResourceServer", query="delete from ResourceEntity r where r.resourceServer.id = :serverId")
|
@NamedQuery(name="deleteResourceByResourceServer", query="delete from ResourceEntity r where r.resourceServer.id = :serverId")
|
||||||
|
|
|
@ -18,9 +18,11 @@ package org.keycloak.authorization.jpa.store;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.FlushModeType;
|
import javax.persistence.FlushModeType;
|
||||||
|
@ -102,9 +104,15 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
|
||||||
|
|
||||||
List<String> result = query.getResultList();
|
List<String> result = query.getResultList();
|
||||||
List<PermissionTicket> list = new LinkedList<>();
|
List<PermissionTicket> list = new LinkedList<>();
|
||||||
|
PermissionTicketStore ticketStore = provider.getStoreFactory().getPermissionTicketStore();
|
||||||
|
|
||||||
for (String id : result) {
|
for (String id : result) {
|
||||||
list.add(provider.getStoreFactory().getPermissionTicketStore().findById(id, resourceServerId));
|
PermissionTicket ticket = ticketStore.findById(id, resourceServerId);
|
||||||
|
if (Objects.nonNull(ticket)) {
|
||||||
|
list.add(ticket);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,9 +126,15 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
|
||||||
|
|
||||||
List<String> result = query.getResultList();
|
List<String> result = query.getResultList();
|
||||||
List<PermissionTicket> list = new LinkedList<>();
|
List<PermissionTicket> list = new LinkedList<>();
|
||||||
|
PermissionTicketStore ticketStore = provider.getStoreFactory().getPermissionTicketStore();
|
||||||
|
|
||||||
for (String id : result) {
|
for (String id : result) {
|
||||||
list.add(provider.getStoreFactory().getPermissionTicketStore().findById(id, resourceServerId));
|
PermissionTicket ticket = ticketStore.findById(id, resourceServerId);
|
||||||
|
if (Objects.nonNull(ticket)) {
|
||||||
|
list.add(ticket);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,9 +153,15 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
|
||||||
|
|
||||||
List<String> result = query.getResultList();
|
List<String> result = query.getResultList();
|
||||||
List<PermissionTicket> list = new LinkedList<>();
|
List<PermissionTicket> list = new LinkedList<>();
|
||||||
|
PermissionTicketStore ticketStore = provider.getStoreFactory().getPermissionTicketStore();
|
||||||
|
|
||||||
for (String id : result) {
|
for (String id : result) {
|
||||||
list.add(provider.getStoreFactory().getPermissionTicketStore().findById(id, resourceServerId));
|
PermissionTicket ticket = ticketStore.findById(id, resourceServerId);
|
||||||
|
if (Objects.nonNull(ticket)) {
|
||||||
|
list.add(ticket);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,6 +204,8 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
|
||||||
}
|
}
|
||||||
} else if (PermissionTicket.REQUESTER_IS_NULL.equals(name)) {
|
} else if (PermissionTicket.REQUESTER_IS_NULL.equals(name)) {
|
||||||
predicates.add(builder.isNull(root.get("requester")));
|
predicates.add(builder.isNull(root.get("requester")));
|
||||||
|
} else if (PermissionTicket.POLICY_IS_NOT_NULL.equals(name)) {
|
||||||
|
predicates.add(builder.isNotNull(root.get("policy")));
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Unsupported filter [" + name + "]");
|
throw new RuntimeException("Unsupported filter [" + name + "]");
|
||||||
}
|
}
|
||||||
|
@ -196,21 +218,35 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
|
||||||
if (firstResult != -1) {
|
if (firstResult != -1) {
|
||||||
query.setFirstResult(firstResult);
|
query.setFirstResult(firstResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxResult != -1) {
|
if (maxResult != -1) {
|
||||||
query.setMaxResults(maxResult);
|
query.setMaxResults(maxResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> result = query.getResultList();
|
List<String> result = query.getResultList();
|
||||||
List<PermissionTicket> list = new LinkedList<>();
|
List<PermissionTicket> list = new LinkedList<>();
|
||||||
PermissionTicketStore ticket = provider.getStoreFactory().getPermissionTicketStore();
|
PermissionTicketStore ticketStore = provider.getStoreFactory().getPermissionTicketStore();
|
||||||
|
|
||||||
for (String id : result) {
|
for (String id : result) {
|
||||||
list.add(ticket.findById(id, resourceServerId));
|
PermissionTicket ticket = ticketStore.findById(id, resourceServerId);
|
||||||
|
if (Objects.nonNull(ticket)) {
|
||||||
|
list.add(ticket);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PermissionTicket> findGranted(String userId, String resourceServerId) {
|
||||||
|
HashMap<String, String> filters = new HashMap<>();
|
||||||
|
|
||||||
|
filters.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
|
||||||
|
filters.put(PermissionTicket.REQUESTER, userId);
|
||||||
|
|
||||||
|
return find(filters, resourceServerId, -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PermissionTicket> findByOwner(String owner, String resourceServerId) {
|
public List<PermissionTicket> findByOwner(String owner, String resourceServerId) {
|
||||||
TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByType", String.class);
|
TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByType", String.class);
|
||||||
|
@ -221,9 +257,15 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
|
||||||
|
|
||||||
List<String> result = query.getResultList();
|
List<String> result = query.getResultList();
|
||||||
List<PermissionTicket> list = new LinkedList<>();
|
List<PermissionTicket> list = new LinkedList<>();
|
||||||
|
PermissionTicketStore ticketStore = provider.getStoreFactory().getPermissionTicketStore();
|
||||||
|
|
||||||
for (String id : result) {
|
for (String id : result) {
|
||||||
list.add(provider.getStoreFactory().getPermissionTicketStore().findById(id, resourceServerId));
|
PermissionTicket ticket = ticketStore.findById(id, resourceServerId);
|
||||||
|
if (Objects.nonNull(ticket)) {
|
||||||
|
list.add(ticket);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,11 +142,21 @@ public class JPAPolicyStore implements PolicyStore {
|
||||||
}
|
}
|
||||||
} else if ("id".equals(name)) {
|
} else if ("id".equals(name)) {
|
||||||
predicates.add(root.get(name).in(value));
|
predicates.add(root.get(name).in(value));
|
||||||
|
} else if ("owner".equals(name)) {
|
||||||
|
predicates.add(root.get(name).in(value));
|
||||||
|
} else if ("owner_is_not_null".equals(name)) {
|
||||||
|
predicates.add(builder.isNotNull(root.get("owner")));
|
||||||
|
} else if ("resource".equals(name)) {
|
||||||
|
predicates.add(root.join("resources").get("id").in(value));
|
||||||
} else {
|
} else {
|
||||||
predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%"));
|
predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!attributes.containsKey("owner") && !attributes.containsKey("owner_is_not_null")) {
|
||||||
|
predicates.add(builder.isNull(root.get("owner")));
|
||||||
|
}
|
||||||
|
|
||||||
querybuilder.where(predicates.toArray(new Predicate[predicates.size()])).orderBy(builder.asc(root.get("name")));
|
querybuilder.where(predicates.toArray(new Predicate[predicates.size()])).orderBy(builder.asc(root.get("name")));
|
||||||
|
|
||||||
Query query = entityManager.createQuery(querybuilder);
|
Query query = entityManager.createQuery(querybuilder);
|
||||||
|
|
|
@ -267,6 +267,7 @@ public class JPAResourceStore implements ResourceStore {
|
||||||
|
|
||||||
query.setFlushMode(FlushModeType.COMMIT);
|
query.setFlushMode(FlushModeType.COMMIT);
|
||||||
query.setParameter("type", type);
|
query.setParameter("type", type);
|
||||||
|
query.setParameter("ownerId", resourceServerId);
|
||||||
query.setParameter("serverId", resourceServerId);
|
query.setParameter("serverId", resourceServerId);
|
||||||
|
|
||||||
List<String> result = query.getResultList();
|
List<String> result = query.getResultList();
|
||||||
|
|
|
@ -16,9 +16,12 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.authorization.jpa.store;
|
package org.keycloak.authorization.jpa.store;
|
||||||
|
|
||||||
|
import static org.keycloak.authorization.UserManagedPermissionUtil.updatePolicy;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
import org.keycloak.authorization.jpa.entities.PermissionTicketEntity;
|
import org.keycloak.authorization.jpa.entities.PermissionTicketEntity;
|
||||||
|
import org.keycloak.authorization.jpa.entities.PolicyEntity;
|
||||||
import org.keycloak.authorization.jpa.entities.ScopeEntity;
|
import org.keycloak.authorization.jpa.entities.ScopeEntity;
|
||||||
import org.keycloak.authorization.model.PermissionTicket;
|
import org.keycloak.authorization.model.PermissionTicket;
|
||||||
import org.keycloak.authorization.model.Policy;
|
import org.keycloak.authorization.model.Policy;
|
||||||
|
@ -34,13 +37,13 @@ import org.keycloak.models.jpa.JpaModel;
|
||||||
*/
|
*/
|
||||||
public class PermissionTicketAdapter implements PermissionTicket, JpaModel<PermissionTicketEntity> {
|
public class PermissionTicketAdapter implements PermissionTicket, JpaModel<PermissionTicketEntity> {
|
||||||
|
|
||||||
private PermissionTicketEntity entity;
|
private final EntityManager entityManager;
|
||||||
private EntityManager em;
|
private final PermissionTicketEntity entity;
|
||||||
private StoreFactory storeFactory;
|
private final StoreFactory storeFactory;
|
||||||
|
|
||||||
public PermissionTicketAdapter(PermissionTicketEntity entity, EntityManager em, StoreFactory storeFactory) {
|
public PermissionTicketAdapter(PermissionTicketEntity entity, EntityManager entityManager, StoreFactory storeFactory) {
|
||||||
this.entity = entity;
|
this.entity = entity;
|
||||||
this.em = em;
|
this.entityManager = entityManager;
|
||||||
this.storeFactory = storeFactory;
|
this.storeFactory = storeFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +85,7 @@ public class PermissionTicketAdapter implements PermissionTicket, JpaModel<Permi
|
||||||
@Override
|
@Override
|
||||||
public void setGrantedTimestamp(Long millis) {
|
public void setGrantedTimestamp(Long millis) {
|
||||||
entity.setGrantedTimestamp(millis);
|
entity.setGrantedTimestamp(millis);
|
||||||
|
updatePolicy(this, storeFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -89,6 +93,24 @@ public class PermissionTicketAdapter implements PermissionTicket, JpaModel<Permi
|
||||||
return storeFactory.getResourceServerStore().findById(entity.getResourceServer().getId());
|
return storeFactory.getResourceServerStore().findById(entity.getResourceServer().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Policy getPolicy() {
|
||||||
|
PolicyEntity policy = entity.getPolicy();
|
||||||
|
|
||||||
|
if (policy == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return storeFactory.getPolicyStore().findById(policy.getId(), entity.getResourceServer().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPolicy(Policy policy) {
|
||||||
|
if (policy != null) {
|
||||||
|
entity.setPolicy(entityManager.getReference(PolicyEntity.class, policy.getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Resource getResource() {
|
public Resource getResource() {
|
||||||
return storeFactory.getResourceStore().findById(entity.getResource().getId(), getResourceServer().getId());
|
return storeFactory.getResourceStore().findById(entity.getResource().getId(), getResourceServer().getId());
|
||||||
|
|
|
@ -207,6 +207,16 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
|
||||||
entity.getResources().remove(ResourceAdapter.toEntity(em, resource));
|
entity.getResources().remove(ResourceAdapter.toEntity(em, resource));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOwner(String owner) {
|
||||||
|
entity.setOwner(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOwner() {
|
||||||
|
return entity.getOwner();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
33
model/jpa/src/main/resources/META-INF/jpa-changelog-authz-4.0.0.Beta3.xml
Executable file
33
model/jpa/src/main/resources/META-INF/jpa-changelog-authz-4.0.0.Beta3.xml
Executable file
|
@ -0,0 +1,33 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!--
|
||||||
|
~ * Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
~ * and other contributors as indicated by the @author tags.
|
||||||
|
~ *
|
||||||
|
~ * Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ * you may not use this file except in compliance with the License.
|
||||||
|
~ * You may obtain a copy of the License at
|
||||||
|
~ *
|
||||||
|
~ * http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~ *
|
||||||
|
~ * Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ * distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ * See the License for the specific language governing permissions and
|
||||||
|
~ * limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<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="psilva@redhat.com" id="authz-4.0.0.Beta3">
|
||||||
|
<addColumn tableName="RESOURCE_SERVER_POLICY">
|
||||||
|
<column name="OWNER" type="VARCHAR(36)">
|
||||||
|
<constraints nullable="true"/>
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
<addColumn tableName="RESOURCE_SERVER_PERM_TICKET">
|
||||||
|
<column name="POLICY_ID" type="VARCHAR(36)">
|
||||||
|
<constraints nullable="true"/>
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
<addForeignKeyConstraint baseColumnNames="POLICY_ID" baseTableName="RESOURCE_SERVER_PERM_TICKET" constraintName="FK_FRSRPO2128CX4WNKOG82SSRFY" referencedColumnNames="ID" referencedTableName="RESOURCE_SERVER_POLICY"/>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
|
@ -55,4 +55,5 @@
|
||||||
<include file="META-INF/jpa-changelog-3.4.2.xml"/>
|
<include file="META-INF/jpa-changelog-3.4.2.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-4.0.0.xml"/>
|
<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.CR1.xml"/>
|
||||||
|
<include file="META-INF/jpa-changelog-authz-4.0.0.Beta3.xml"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|
|
@ -235,7 +235,7 @@ public final class AuthorizationProvider implements Provider {
|
||||||
@Override
|
@Override
|
||||||
public void delete(String id) {
|
public void delete(String id) {
|
||||||
Scope scope = findById(id, null);
|
Scope scope = findById(id, null);
|
||||||
PermissionTicketStore ticketStore = storeFactory.getPermissionTicketStore();
|
PermissionTicketStore ticketStore = AuthorizationProvider.this.getStoreFactory().getPermissionTicketStore();
|
||||||
List<PermissionTicket> permissions = ticketStore.findByScope(id, scope.getResourceServer().getId());
|
List<PermissionTicket> permissions = ticketStore.findByScope(id, scope.getResourceServer().getId());
|
||||||
|
|
||||||
for (PermissionTicket permission : permissions) {
|
for (PermissionTicket permission : permissions) {
|
||||||
|
@ -414,6 +414,7 @@ public final class AuthorizationProvider implements Provider {
|
||||||
@Override
|
@Override
|
||||||
public void delete(String id) {
|
public void delete(String id) {
|
||||||
Resource resource = findById(id, null);
|
Resource resource = findById(id, null);
|
||||||
|
StoreFactory storeFactory = AuthorizationProvider.this.getStoreFactory();
|
||||||
PermissionTicketStore ticketStore = storeFactory.getPermissionTicketStore();
|
PermissionTicketStore ticketStore = storeFactory.getPermissionTicketStore();
|
||||||
List<PermissionTicket> permissions = ticketStore.findByResource(id, resource.getResourceServer().getId());
|
List<PermissionTicket> permissions = ticketStore.findByResource(id, resource.getResourceServer().getId());
|
||||||
|
|
||||||
|
@ -421,6 +422,17 @@ public final class AuthorizationProvider implements Provider {
|
||||||
ticketStore.delete(permission.getId());
|
ticketStore.delete(permission.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PolicyStore policyStore = storeFactory.getPolicyStore();
|
||||||
|
List<Policy> policies = policyStore.findByResource(id, resource.getResourceServer().getId());
|
||||||
|
|
||||||
|
for (Policy policyModel : policies) {
|
||||||
|
if (policyModel.getResources().size() == 1) {
|
||||||
|
policyStore.delete(policyModel.getId());
|
||||||
|
} else {
|
||||||
|
policyModel.removeResource(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delegate.delete(id);
|
delegate.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.authorization;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.authorization.model.PermissionTicket;
|
||||||
|
import org.keycloak.authorization.model.Policy;
|
||||||
|
import org.keycloak.authorization.model.Scope;
|
||||||
|
import org.keycloak.authorization.store.PolicyStore;
|
||||||
|
import org.keycloak.authorization.store.StoreFactory;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
||||||
|
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public class UserManagedPermissionUtil {
|
||||||
|
|
||||||
|
public static void updatePolicy(PermissionTicket ticket, StoreFactory storeFactory) {
|
||||||
|
Scope scope = ticket.getScope();
|
||||||
|
Policy policy = ticket.getPolicy();
|
||||||
|
|
||||||
|
if (policy == null) {
|
||||||
|
HashMap<String, String> filter = new HashMap<>();
|
||||||
|
|
||||||
|
filter.put(PermissionTicket.OWNER, ticket.getOwner());
|
||||||
|
filter.put(PermissionTicket.REQUESTER, ticket.getRequester());
|
||||||
|
filter.put(PermissionTicket.RESOURCE, ticket.getResource().getId());
|
||||||
|
filter.put(PermissionTicket.POLICY_IS_NOT_NULL, Boolean.TRUE.toString());
|
||||||
|
|
||||||
|
List<PermissionTicket> tickets = storeFactory.getPermissionTicketStore().find(filter, ticket.getResourceServer().getId(), -1, 1);
|
||||||
|
|
||||||
|
if (!tickets.isEmpty()) {
|
||||||
|
policy = tickets.iterator().next().getPolicy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticket.isGranted()) {
|
||||||
|
if (policy == null) {
|
||||||
|
policy = createUserManagedPermission(ticket, storeFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scope != null && !policy.getScopes().contains(scope)) {
|
||||||
|
policy.addScope(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
ticket.setPolicy(policy);
|
||||||
|
} else if (scope != null) {
|
||||||
|
policy.removeScope(scope);
|
||||||
|
ticket.setPolicy(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removePolicy(PermissionTicket ticket, StoreFactory storeFactory) {
|
||||||
|
Policy policy = ticket.getPolicy();
|
||||||
|
|
||||||
|
if (policy != null) {
|
||||||
|
HashMap<String, String> filter = new HashMap<>();
|
||||||
|
|
||||||
|
filter.put(PermissionTicket.OWNER, ticket.getOwner());
|
||||||
|
filter.put(PermissionTicket.REQUESTER, ticket.getRequester());
|
||||||
|
filter.put(PermissionTicket.RESOURCE, ticket.getResource().getId());
|
||||||
|
filter.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
|
||||||
|
|
||||||
|
List<PermissionTicket> tickets = storeFactory.getPermissionTicketStore().find(filter, ticket.getResourceServer().getId(), -1, -1);
|
||||||
|
|
||||||
|
if (tickets.isEmpty()) {
|
||||||
|
PolicyStore policyStore = storeFactory.getPolicyStore();
|
||||||
|
|
||||||
|
for (Policy associatedPolicy : policy.getAssociatedPolicies()) {
|
||||||
|
policyStore.delete(associatedPolicy.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
policyStore.delete(policy.getId());
|
||||||
|
} else if (ticket.getScope() != null) {
|
||||||
|
policy.removeScope(ticket.getScope());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Policy createUserManagedPermission(PermissionTicket ticket, StoreFactory storeFactory) {
|
||||||
|
PolicyStore policyStore = storeFactory.getPolicyStore();
|
||||||
|
UserPolicyRepresentation userPolicyRep = new UserPolicyRepresentation();
|
||||||
|
|
||||||
|
userPolicyRep.setName(KeycloakModelUtils.generateId());
|
||||||
|
userPolicyRep.addUser(ticket.getRequester());
|
||||||
|
|
||||||
|
Policy userPolicy = policyStore.create(userPolicyRep, ticket.getResourceServer());
|
||||||
|
|
||||||
|
userPolicy.setOwner(ticket.getOwner());
|
||||||
|
|
||||||
|
PolicyRepresentation policyRep = new PolicyRepresentation();
|
||||||
|
|
||||||
|
policyRep.setName(KeycloakModelUtils.generateId());
|
||||||
|
policyRep.setType("uma");
|
||||||
|
policyRep.addPolicy(userPolicy.getId());
|
||||||
|
|
||||||
|
Policy policy = policyStore.create(policyRep, ticket.getResourceServer());
|
||||||
|
|
||||||
|
policy.setOwner(ticket.getOwner());
|
||||||
|
policy.addResource(ticket.getResource());
|
||||||
|
|
||||||
|
Scope scope = ticket.getScope();
|
||||||
|
|
||||||
|
if (scope != null) {
|
||||||
|
policy.addScope(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ public interface PermissionTicket {
|
||||||
String GRANTED = "granted";
|
String GRANTED = "granted";
|
||||||
String REQUESTER = "requester";
|
String REQUESTER = "requester";
|
||||||
String REQUESTER_IS_NULL = "requester_is_null";
|
String REQUESTER_IS_NULL = "requester_is_null";
|
||||||
|
String POLICY_IS_NOT_NULL = "policy_is_not_null";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the unique identifier for this instance.
|
* Returns the unique identifier for this instance.
|
||||||
|
@ -73,4 +74,8 @@ public interface PermissionTicket {
|
||||||
* @return a resource server
|
* @return a resource server
|
||||||
*/
|
*/
|
||||||
ResourceServer getResourceServer();
|
ResourceServer getResourceServer();
|
||||||
|
|
||||||
|
Policy getPolicy();
|
||||||
|
|
||||||
|
void setPolicy(Policy policy);
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,6 +148,10 @@ public interface Policy {
|
||||||
*/
|
*/
|
||||||
Set<Scope> getScopes();
|
Set<Scope> getScopes();
|
||||||
|
|
||||||
|
String getOwner();
|
||||||
|
|
||||||
|
void setOwner(String owner);
|
||||||
|
|
||||||
void addScope(Scope scope);
|
void addScope(Scope scope);
|
||||||
|
|
||||||
void removeScope(Scope scope);
|
void removeScope(Scope scope);
|
||||||
|
|
|
@ -48,12 +48,21 @@ public class DefaultEvaluation implements Evaluation {
|
||||||
private final ResourcePermission permission;
|
private final ResourcePermission permission;
|
||||||
private final EvaluationContext executionContext;
|
private final EvaluationContext executionContext;
|
||||||
private final Decision decision;
|
private final Decision decision;
|
||||||
private final Policy policy;
|
private Policy policy;
|
||||||
private final Policy parentPolicy;
|
private final Policy parentPolicy;
|
||||||
private final AuthorizationProvider authorizationProvider;
|
private final AuthorizationProvider authorizationProvider;
|
||||||
private final Realm realm;
|
private final Realm realm;
|
||||||
private Effect effect;
|
private Effect effect;
|
||||||
|
|
||||||
|
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Decision decision, AuthorizationProvider authorizationProvider) {
|
||||||
|
this.permission = permission;
|
||||||
|
this.executionContext = executionContext;
|
||||||
|
this.parentPolicy = parentPolicy;
|
||||||
|
this.decision = decision;
|
||||||
|
this.authorizationProvider = authorizationProvider;
|
||||||
|
this.realm = createRealm();
|
||||||
|
}
|
||||||
|
|
||||||
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Policy policy, Decision decision, AuthorizationProvider authorizationProvider) {
|
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Policy policy, Decision decision, AuthorizationProvider authorizationProvider) {
|
||||||
this.permission = permission;
|
this.permission = permission;
|
||||||
this.executionContext = executionContext;
|
this.executionContext = executionContext;
|
||||||
|
@ -98,6 +107,9 @@ public class DefaultEvaluation implements Evaluation {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Policy getPolicy() {
|
public Policy getPolicy() {
|
||||||
|
if (policy == null) {
|
||||||
|
return parentPolicy;
|
||||||
|
}
|
||||||
return this.policy;
|
return this.policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +131,8 @@ public class DefaultEvaluation implements Evaluation {
|
||||||
return effect;
|
return effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
void denyIfNoEffect() {
|
@Override
|
||||||
|
public void denyIfNoEffect() {
|
||||||
if (this.effect == null) {
|
if (this.effect == null) {
|
||||||
deny();
|
deny();
|
||||||
}
|
}
|
||||||
|
@ -248,4 +261,8 @@ public class DefaultEvaluation implements Evaluation {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPolicy(Policy policy) {
|
||||||
|
this.policy = policy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator {
|
||||||
PolicyEnforcementMode enforcementMode = resourceServer.getPolicyEnforcementMode();
|
PolicyEnforcementMode enforcementMode = resourceServer.getPolicyEnforcementMode();
|
||||||
|
|
||||||
if (PolicyEnforcementMode.DISABLED.equals(enforcementMode)) {
|
if (PolicyEnforcementMode.DISABLED.equals(enforcementMode)) {
|
||||||
createEvaluation(permission, executionContext, decision, null, null).grant();
|
createEvaluation(permission, executionContext, decision, null).grant();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PolicyEnforcementMode.PERMISSIVE.equals(enforcementMode) && !verified.get()) {
|
if (PolicyEnforcementMode.PERMISSIVE.equals(enforcementMode) && !verified.get()) {
|
||||||
createEvaluation(permission, executionContext, decision, null, null).grant();
|
createEvaluation(permission, executionContext, decision, null).grant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,25 +113,22 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Policy associatedPolicy : parentPolicy.getAssociatedPolicies()) {
|
PolicyProvider policyProvider = authorization.getProvider(parentPolicy.getType());
|
||||||
PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType());
|
|
||||||
|
|
||||||
if (policyProvider == null) {
|
if (policyProvider == null) {
|
||||||
throw new RuntimeException("Unknown parentPolicy provider for type [" + associatedPolicy.getType() + "].");
|
throw new RuntimeException("Unknown parentPolicy provider for type [" + parentPolicy.getType() + "].");
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultEvaluation evaluation = createEvaluation(permission, executionContext, decision, parentPolicy, associatedPolicy);
|
DefaultEvaluation evaluation = createEvaluation(permission, executionContext, decision, parentPolicy);
|
||||||
|
|
||||||
policyProvider.evaluate(evaluation);
|
policyProvider.evaluate(evaluation);
|
||||||
evaluation.denyIfNoEffect();
|
|
||||||
}
|
|
||||||
|
|
||||||
verified.compareAndSet(false, true);
|
verified.compareAndSet(false, true);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefaultEvaluation createEvaluation(ResourcePermission permission, EvaluationContext executionContext, Decision decision, Policy parentPolicy, Policy associatedPolicy) {
|
private DefaultEvaluation createEvaluation(ResourcePermission permission, EvaluationContext executionContext, Decision decision, Policy parentPolicy) {
|
||||||
return new DefaultEvaluation(permission, executionContext, parentPolicy, associatedPolicy, decision, authorization);
|
return new DefaultEvaluation(permission, executionContext, parentPolicy, decision, authorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasRequestedScopes(final ResourcePermission permission, final Policy policy) {
|
private boolean hasRequestedScopes(final ResourcePermission permission, final Policy policy) {
|
||||||
|
|
|
@ -69,4 +69,9 @@ public interface Evaluation {
|
||||||
* Denies the requested permission.
|
* Denies the requested permission.
|
||||||
*/
|
*/
|
||||||
void deny();
|
void deny();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Denies the requested permission if a decision was not made yet.
|
||||||
|
*/
|
||||||
|
void denyIfNoEffect();
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,53 +57,6 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
|
||||||
this.authorization = authorization;
|
this.authorization = authorization;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDeny(Result result) {
|
|
||||||
ResourcePermission permission = result.getPermission();
|
|
||||||
Resource resource = permission.getResource();
|
|
||||||
|
|
||||||
if (resource != null && resource.isOwnerManagedAccess()) {
|
|
||||||
if (!resource.getOwner().equals(identity.getId())) {
|
|
||||||
Map<String, String> filters = new HashMap<>();
|
|
||||||
|
|
||||||
filters.put(PermissionTicket.RESOURCE, resource.getId());
|
|
||||||
filters.put(PermissionTicket.REQUESTER, identity.getId());
|
|
||||||
filters.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
|
|
||||||
|
|
||||||
List<PermissionTicket> permissions = authorization.getStoreFactory().getPermissionTicketStore().find(filters, resource.getResourceServer().getId(), -1, -1);
|
|
||||||
|
|
||||||
if (!permissions.isEmpty()) {
|
|
||||||
List<Scope> grantedScopes = new ArrayList<>();
|
|
||||||
|
|
||||||
for (PolicyResult policyResult : result.getResults()) {
|
|
||||||
for (PermissionTicket ticket : permissions) {
|
|
||||||
Scope grantedScope = ticket.getScope();
|
|
||||||
|
|
||||||
if ("resource".equals(policyResult.getPolicy().getType())) {
|
|
||||||
policyResult.setStatus(Effect.PERMIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (grantedScope != null) {
|
|
||||||
grantedScopes.add(grantedScope);
|
|
||||||
|
|
||||||
for (Scope policyScope : policyResult.getPolicy().getScopes()) {
|
|
||||||
if (policyScope.equals(grantedScope)) {
|
|
||||||
policyResult.setStatus(Effect.PERMIT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
permission.getScopes().clear();
|
|
||||||
permission.getScopes().addAll(grantedScopes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onDeny(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onComplete() {
|
public void onComplete() {
|
||||||
super.onComplete();
|
super.onComplete();
|
||||||
|
@ -111,9 +64,10 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
|
||||||
if (request.isSubmitRequest()) {
|
if (request.isSubmitRequest()) {
|
||||||
StoreFactory storeFactory = authorization.getStoreFactory();
|
StoreFactory storeFactory = authorization.getStoreFactory();
|
||||||
ResourceStore resourceStore = storeFactory.getResourceStore();
|
ResourceStore resourceStore = storeFactory.getResourceStore();
|
||||||
|
List<PermissionTicketToken.ResourcePermission> resources = ticket.getResources();
|
||||||
|
|
||||||
if (ticket.getResources() != null) {
|
if (resources != null) {
|
||||||
for (PermissionTicketToken.ResourcePermission permission : ticket.getResources()) {
|
for (PermissionTicketToken.ResourcePermission permission : resources) {
|
||||||
Resource resource = resourceStore.findById(permission.getResourceId(), resourceServer.getId());
|
Resource resource = resourceStore.findById(permission.getResourceId(), resourceServer.getId());
|
||||||
|
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
|
|
|
@ -34,6 +34,10 @@ public interface PolicyProviderFactory<R extends AbstractPolicyRepresentation> e
|
||||||
|
|
||||||
String getGroup();
|
String getGroup();
|
||||||
|
|
||||||
|
default boolean isInternal() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
PolicyProvider create(AuthorizationProvider authorization);
|
PolicyProvider create(AuthorizationProvider authorization);
|
||||||
|
|
||||||
R toRepresentation(Policy policy);
|
R toRepresentation(Policy policy);
|
||||||
|
|
|
@ -90,4 +90,13 @@ public interface PermissionTicketStore {
|
||||||
List<PermissionTicket> findByScope(String scopeId, String resourceServerId);
|
List<PermissionTicket> findByScope(String scopeId, String resourceServerId);
|
||||||
|
|
||||||
List<PermissionTicket> find(Map<String, String> attributes, String resourceServerId, int firstResult, int maxResult);
|
List<PermissionTicket> find(Map<String, String> attributes, String resourceServerId, int firstResult, int maxResult);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of {@link PermissionTicket} granted to the given {@code userId}.
|
||||||
|
*
|
||||||
|
* @param userId the user id
|
||||||
|
* @param resourceServerId the resource server id
|
||||||
|
* @return a list of permissions granted for a particular user
|
||||||
|
*/
|
||||||
|
List<PermissionTicket> findGranted(String userId, String resourceServerId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2444,7 +2444,7 @@ public class RepresentationToModel {
|
||||||
if (granted && !ticket.isGranted()) {
|
if (granted && !ticket.isGranted()) {
|
||||||
ticket.setGrantedTimestamp(System.currentTimeMillis());
|
ticket.setGrantedTimestamp(System.currentTimeMillis());
|
||||||
} else if (!granted) {
|
} else if (!granted) {
|
||||||
ticket.setGrantedTimestamp(null);
|
ticketStore.delete(ticket.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
return ticket;
|
return ticket;
|
||||||
|
|
|
@ -55,7 +55,6 @@ import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
|
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.PolicyProviderRepresentation;
|
import org.keycloak.representations.idm.authorization.PolicyProviderRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||||
import org.keycloak.services.resources.admin.AdminEventBuilder;
|
import org.keycloak.services.resources.admin.AdminEventBuilder;
|
||||||
|
@ -253,12 +252,13 @@ public class PolicyService {
|
||||||
this.auth.realm().requireViewAuthorization();
|
this.auth.realm().requireViewAuthorization();
|
||||||
return Response.ok(
|
return Response.ok(
|
||||||
authorization.getProviderFactories().stream()
|
authorization.getProviderFactories().stream()
|
||||||
.map(provider -> {
|
.filter(factory -> !factory.isInternal())
|
||||||
|
.map(factory -> {
|
||||||
PolicyProviderRepresentation representation = new PolicyProviderRepresentation();
|
PolicyProviderRepresentation representation = new PolicyProviderRepresentation();
|
||||||
|
|
||||||
representation.setName(provider.getName());
|
representation.setName(factory.getName());
|
||||||
representation.setGroup(provider.getGroup());
|
representation.setGroup(factory.getGroup());
|
||||||
representation.setType(provider.getId());
|
representation.setType(factory.getId());
|
||||||
|
|
||||||
return representation;
|
return representation;
|
||||||
})
|
})
|
||||||
|
|
|
@ -164,17 +164,6 @@ public class ResourceSetService {
|
||||||
return Response.status(Status.NOT_FOUND).build();
|
return Response.status(Status.NOT_FOUND).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
PolicyStore policyStore = storeFactory.getPolicyStore();
|
|
||||||
List<Policy> policies = policyStore.findByResource(id, resourceServer.getId());
|
|
||||||
|
|
||||||
for (Policy policyModel : policies) {
|
|
||||||
if (policyModel.getResources().size() == 1) {
|
|
||||||
policyStore.delete(policyModel.getId());
|
|
||||||
} else {
|
|
||||||
policyModel.removeResource(resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
storeFactory.getResourceStore().delete(id);
|
storeFactory.getResourceStore().delete(id);
|
||||||
|
|
||||||
if (authorization.getRealm().isAdminEventsEnabled()) {
|
if (authorization.getRealm().isAdminEventsEnabled()) {
|
||||||
|
@ -254,7 +243,8 @@ public class ResourceSetService {
|
||||||
public Response getPermissions(@PathParam("id") String id) {
|
public Response getPermissions(@PathParam("id") String id) {
|
||||||
requireView();
|
requireView();
|
||||||
StoreFactory storeFactory = authorization.getStoreFactory();
|
StoreFactory storeFactory = authorization.getStoreFactory();
|
||||||
Resource model = storeFactory.getResourceStore().findById(id, resourceServer.getId());
|
ResourceStore resourceStore = storeFactory.getResourceStore();
|
||||||
|
Resource model = resourceStore.findById(id, resourceServer.getId());
|
||||||
|
|
||||||
if (model == null) {
|
if (model == null) {
|
||||||
return Response.status(Status.NOT_FOUND).build();
|
return Response.status(Status.NOT_FOUND).build();
|
||||||
|
@ -264,13 +254,27 @@ public class ResourceSetService {
|
||||||
Set<Policy> policies = new HashSet<>();
|
Set<Policy> policies = new HashSet<>();
|
||||||
|
|
||||||
policies.addAll(policyStore.findByResource(model.getId(), resourceServer.getId()));
|
policies.addAll(policyStore.findByResource(model.getId(), resourceServer.getId()));
|
||||||
|
|
||||||
|
if (model.getType() != null) {
|
||||||
policies.addAll(policyStore.findByResourceType(model.getType(), resourceServer.getId()));
|
policies.addAll(policyStore.findByResourceType(model.getType(), resourceServer.getId()));
|
||||||
|
|
||||||
|
HashMap<String, String[]> resourceFilter = new HashMap<>();
|
||||||
|
|
||||||
|
resourceFilter.put("owner", new String[]{resourceServer.getId()});
|
||||||
|
resourceFilter.put("type", new String[]{model.getType()});
|
||||||
|
|
||||||
|
for (Resource resourceType : resourceStore.findByResourceServer(resourceFilter, resourceServer.getId(), -1, -1)) {
|
||||||
|
policies.addAll(policyStore.findByResource(resourceType.getId(), resourceServer.getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
policies.addAll(policyStore.findByScopeIds(model.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toList()), id, resourceServer.getId()));
|
policies.addAll(policyStore.findByScopeIds(model.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toList()), id, resourceServer.getId()));
|
||||||
policies.addAll(policyStore.findByScopeIds(model.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toList()), null, resourceServer.getId()));
|
policies.addAll(policyStore.findByScopeIds(model.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toList()), null, resourceServer.getId()));
|
||||||
|
|
||||||
List<PolicyRepresentation> representation = new ArrayList<>();
|
List<PolicyRepresentation> representation = new ArrayList<>();
|
||||||
|
|
||||||
for (Policy policyModel : policies) {
|
for (Policy policyModel : policies) {
|
||||||
|
if (!"uma".equalsIgnoreCase(policyModel.getType())) {
|
||||||
PolicyRepresentation policy = new PolicyRepresentation();
|
PolicyRepresentation policy = new PolicyRepresentation();
|
||||||
|
|
||||||
policy.setId(policyModel.getId());
|
policy.setId(policyModel.getId());
|
||||||
|
@ -281,6 +285,7 @@ public class ResourceSetService {
|
||||||
representation.add(policy);
|
representation.add(policy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Response.ok(representation).build();
|
return Response.ok(representation).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,10 @@ import org.keycloak.authorization.common.KeycloakIdentity;
|
||||||
import org.keycloak.authorization.model.PermissionTicket;
|
import org.keycloak.authorization.model.PermissionTicket;
|
||||||
import org.keycloak.authorization.model.ResourceServer;
|
import org.keycloak.authorization.model.ResourceServer;
|
||||||
import org.keycloak.authorization.store.PermissionTicketStore;
|
import org.keycloak.authorization.store.PermissionTicketStore;
|
||||||
|
import org.keycloak.authorization.store.StoreFactory;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.models.utils.RepresentationToModel;
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
import org.keycloak.representations.idm.authorization.PermissionTicketRepresentation;
|
import org.keycloak.representations.idm.authorization.PermissionTicketRepresentation;
|
||||||
|
@ -182,7 +185,8 @@ public class PermissionTicketService {
|
||||||
@QueryParam("returnNames") Boolean returnNames,
|
@QueryParam("returnNames") Boolean returnNames,
|
||||||
@QueryParam("first") Integer firstResult,
|
@QueryParam("first") Integer firstResult,
|
||||||
@QueryParam("max") Integer maxResult) {
|
@QueryParam("max") Integer maxResult) {
|
||||||
PermissionTicketStore permissionTicketStore = authorization.getStoreFactory().getPermissionTicketStore();
|
StoreFactory storeFactory = authorization.getStoreFactory();
|
||||||
|
PermissionTicketStore permissionTicketStore = storeFactory.getPermissionTicketStore();
|
||||||
|
|
||||||
Map<String, String> filters = new HashMap<>();
|
Map<String, String> filters = new HashMap<>();
|
||||||
|
|
||||||
|
@ -191,15 +195,22 @@ public class PermissionTicketService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scopeId != null) {
|
if (scopeId != null) {
|
||||||
filters.put(PermissionTicket.SCOPE, scopeId);
|
ScopeStore scopeStore = storeFactory.getScopeStore();
|
||||||
|
Scope scope = scopeStore.findById(scopeId, resourceServer.getId());
|
||||||
|
|
||||||
|
if (scope == null) {
|
||||||
|
scope = scopeStore.findByName(scopeId, resourceServer.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
filters.put(PermissionTicket.SCOPE, scope != null ? scope.getId() : scopeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (owner != null) {
|
if (owner != null) {
|
||||||
filters.put(PermissionTicket.OWNER, owner);
|
filters.put(PermissionTicket.OWNER, getUserId(owner));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requester != null) {
|
if (requester != null) {
|
||||||
filters.put(PermissionTicket.REQUESTER, requester);
|
filters.put(PermissionTicket.REQUESTER, getUserId(requester));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (granted != null) {
|
if (granted != null) {
|
||||||
|
@ -212,4 +223,22 @@ public class PermissionTicketService {
|
||||||
.collect(Collectors.toList()))
|
.collect(Collectors.toList()))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getUserId(String userIdOrName) {
|
||||||
|
UserProvider userProvider = authorization.getKeycloakSession().users();
|
||||||
|
RealmModel realm = authorization.getRealm();
|
||||||
|
UserModel userModel = userProvider.getUserById(userIdOrName, realm);
|
||||||
|
|
||||||
|
if (userModel != null) {
|
||||||
|
return userModel.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
userModel = userProvider.getUserByUsername(userIdOrName, realm);
|
||||||
|
|
||||||
|
if (userModel != null) {
|
||||||
|
return userModel.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return userIdOrName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.authorization.util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -33,6 +34,7 @@ import javax.ws.rs.core.Response.Status;
|
||||||
import org.keycloak.authorization.AuthorizationProvider;
|
import org.keycloak.authorization.AuthorizationProvider;
|
||||||
import org.keycloak.authorization.Decision.Effect;
|
import org.keycloak.authorization.Decision.Effect;
|
||||||
import org.keycloak.authorization.identity.Identity;
|
import org.keycloak.authorization.identity.Identity;
|
||||||
|
import org.keycloak.authorization.model.PermissionTicket;
|
||||||
import org.keycloak.authorization.model.Policy;
|
import org.keycloak.authorization.model.Policy;
|
||||||
import org.keycloak.authorization.model.Resource;
|
import org.keycloak.authorization.model.Resource;
|
||||||
import org.keycloak.authorization.model.ResourceServer;
|
import org.keycloak.authorization.model.ResourceServer;
|
||||||
|
@ -71,9 +73,22 @@ public final class Permissions {
|
||||||
StoreFactory storeFactory = authorization.getStoreFactory();
|
StoreFactory storeFactory = authorization.getStoreFactory();
|
||||||
ResourceStore resourceStore = storeFactory.getResourceStore();
|
ResourceStore resourceStore = storeFactory.getResourceStore();
|
||||||
|
|
||||||
|
// obtain all resources where owner is the resource server
|
||||||
resourceStore.findByOwner(resourceServer.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization)));
|
resourceStore.findByOwner(resourceServer.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization)));
|
||||||
|
|
||||||
|
// obtain all resources where owner is the current user
|
||||||
resourceStore.findByOwner(identity.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization)));
|
resourceStore.findByOwner(identity.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization)));
|
||||||
|
|
||||||
|
// obtain all resources granted to the user via permission tickets (uma)
|
||||||
|
List<PermissionTicket> tickets = storeFactory.getPermissionTicketStore().findGranted(identity.getId(), resourceServer.getId());
|
||||||
|
Map<String, ResourcePermission> userManagedPermissions = new HashMap<>();
|
||||||
|
|
||||||
|
for (PermissionTicket ticket : tickets) {
|
||||||
|
userManagedPermissions.computeIfAbsent(ticket.getResource().getId(), id -> new ResourcePermission(ticket.getResource(), new ArrayList<>(), resourceServer));
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions.addAll(userManagedPermissions.values());
|
||||||
|
|
||||||
return permissions;
|
return permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +171,9 @@ public final class Permissions {
|
||||||
boolean resourceDenied = false;
|
boolean resourceDenied = false;
|
||||||
ResourcePermission permission = result.getPermission();
|
ResourcePermission permission = result.getPermission();
|
||||||
List<Result.PolicyResult> results = result.getResults();
|
List<Result.PolicyResult> results = result.getResults();
|
||||||
|
List<Result.PolicyResult> userManagedPermissions = new ArrayList<>();
|
||||||
int deniedCount = results.size();
|
int deniedCount = results.size();
|
||||||
|
Resource resource = permission.getResource();
|
||||||
|
|
||||||
for (Result.PolicyResult policyResult : results) {
|
for (Result.PolicyResult policyResult : results) {
|
||||||
Policy policy = policyResult.getPolicy();
|
Policy policy = policyResult.getPolicy();
|
||||||
|
@ -175,6 +192,8 @@ public final class Permissions {
|
||||||
// Later they will be filtered based on any denied scope, if any.
|
// Later they will be filtered based on any denied scope, if any.
|
||||||
// TODO: we could probably provide a configuration option to let users decide whether or not a resource-based permission should grant all scopes associated with the resource.
|
// TODO: we could probably provide a configuration option to let users decide whether or not a resource-based permission should grant all scopes associated with the resource.
|
||||||
grantedScopes.addAll(permission.getScopes());
|
grantedScopes.addAll(permission.getScopes());
|
||||||
|
} if (resource.isOwnerManagedAccess() && "uma".equals(policy.getType())) {
|
||||||
|
userManagedPermissions.add(policyResult);
|
||||||
}
|
}
|
||||||
deniedCount--;
|
deniedCount--;
|
||||||
} else {
|
} else {
|
||||||
|
@ -183,7 +202,7 @@ public final class Permissions {
|
||||||
deniedScopes.addAll(policyScopes);
|
deniedScopes.addAll(policyScopes);
|
||||||
} else if (isResourcePermission(policy)) {
|
} else if (isResourcePermission(policy)) {
|
||||||
resourceDenied = true;
|
resourceDenied = true;
|
||||||
deniedScopes.addAll(permission.getResource().getScopes());
|
deniedScopes.addAll(resource.getScopes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,6 +212,14 @@ public final class Permissions {
|
||||||
grantedScopes.removeAll(deniedScopes);
|
grantedScopes.removeAll(deniedScopes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (Result.PolicyResult policyResult : userManagedPermissions) {
|
||||||
|
Policy policy = policyResult.getPolicy();
|
||||||
|
|
||||||
|
grantedScopes.addAll(policy.getScopes());
|
||||||
|
|
||||||
|
resourceDenied = false;
|
||||||
|
}
|
||||||
|
|
||||||
// if there are no policy results is because the permission didn't match any policy.
|
// if there are no policy results is because the permission didn't match any policy.
|
||||||
// In this case, if results is empty is because we are in permissive mode.
|
// In this case, if results is empty is because we are in permissive mode.
|
||||||
if (!results.isEmpty()) {
|
if (!results.isEmpty()) {
|
||||||
|
|
|
@ -23,7 +23,9 @@ import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@ -40,12 +42,16 @@ import org.keycloak.authorization.client.Configuration;
|
||||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.representations.AccessToken.Authorization;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest.Metadata;
|
import org.keycloak.representations.idm.authorization.AuthorizationRequest.Metadata;
|
||||||
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
||||||
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
|
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.Permission;
|
import org.keycloak.representations.idm.authorization.Permission;
|
||||||
|
import org.keycloak.representations.idm.authorization.PermissionRequest;
|
||||||
|
import org.keycloak.representations.idm.authorization.PermissionResponse;
|
||||||
|
import org.keycloak.representations.idm.authorization.PermissionTicketRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
|
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||||
import org.keycloak.testsuite.util.ClientBuilder;
|
import org.keycloak.testsuite.util.ClientBuilder;
|
||||||
|
@ -390,6 +396,108 @@ public class EntitlementAPITest extends AbstractAuthzTest {
|
||||||
PAIRWISE_AUTHZ_CLIENT_CONFIG);
|
PAIRWISE_AUTHZ_CLIENT_CONFIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObtainAllEntitlements() throws Exception {
|
||||||
|
ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST);
|
||||||
|
AuthorizationResource authorization = client.authorization();
|
||||||
|
|
||||||
|
JSPolicyRepresentation policy = new JSPolicyRepresentation();
|
||||||
|
|
||||||
|
policy.setName("Only Owner Policy");
|
||||||
|
policy.setCode("if ($evaluation.getContext().getIdentity().getId() == $evaluation.getPermission().getResource().getOwner()) {$evaluation.grant();}");
|
||||||
|
|
||||||
|
authorization.policies().js().create(policy).close();
|
||||||
|
|
||||||
|
ResourceRepresentation resource = new ResourceRepresentation();
|
||||||
|
|
||||||
|
resource.setName("Marta Resource");
|
||||||
|
resource.setOwner("marta");
|
||||||
|
resource.setOwnerManagedAccess(true);
|
||||||
|
|
||||||
|
resource = authorization.resources().create(resource).readEntity(ResourceRepresentation.class);
|
||||||
|
|
||||||
|
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
||||||
|
|
||||||
|
permission.setName("Marta Resource Permission");
|
||||||
|
permission.addResource(resource.getId());
|
||||||
|
permission.addPolicy(policy.getName());
|
||||||
|
|
||||||
|
authorization.permissions().resource().create(permission);
|
||||||
|
|
||||||
|
assertTrue(hasPermission("marta", "password", resource.getId()));
|
||||||
|
assertFalse(hasPermission("kolo", "password", resource.getId()));
|
||||||
|
|
||||||
|
String accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "kolo", "password").getAccessToken();
|
||||||
|
AuthzClient authzClient = getAuthzClient(AUTHZ_CLIENT_CONFIG);
|
||||||
|
PermissionResponse permissionResponse = authzClient.protection().permission().create(new PermissionRequest(resource.getId()));
|
||||||
|
AuthorizationRequest request = new AuthorizationRequest();
|
||||||
|
|
||||||
|
request.setTicket(permissionResponse.getTicket());
|
||||||
|
|
||||||
|
try {
|
||||||
|
authzClient.authorization(accessToken).authorize(request);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PermissionTicketRepresentation> tickets = authzClient.protection().permission().findByResource(resource.getId());
|
||||||
|
|
||||||
|
assertEquals(1, tickets.size());
|
||||||
|
|
||||||
|
PermissionTicketRepresentation ticket = tickets.get(0);
|
||||||
|
|
||||||
|
ticket.setGranted(true);
|
||||||
|
|
||||||
|
authzClient.protection().permission().update(ticket);
|
||||||
|
|
||||||
|
assertTrue(hasPermission("kolo", "password", resource.getId()));
|
||||||
|
|
||||||
|
resource.addScope("Scope A");
|
||||||
|
|
||||||
|
authorization.resources().resource(resource.getId()).update(resource);
|
||||||
|
|
||||||
|
// the addition of a new scope invalidates the permission previously grante to the resource
|
||||||
|
assertFalse(hasPermission("kolo", "password", resource.getId()));
|
||||||
|
|
||||||
|
accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "kolo", "password").getAccessToken();
|
||||||
|
permissionResponse = authzClient.protection().permission().create(new PermissionRequest(resource.getId(), "Scope A"));
|
||||||
|
request = new AuthorizationRequest();
|
||||||
|
|
||||||
|
request.setTicket(permissionResponse.getTicket());
|
||||||
|
|
||||||
|
try {
|
||||||
|
authzClient.authorization(accessToken).authorize(request);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
tickets = authzClient.protection().permission().find(resource.getId(), "Scope A", null, null, false, false, null, null);
|
||||||
|
|
||||||
|
assertEquals(1, tickets.size());
|
||||||
|
|
||||||
|
ticket = tickets.get(0);
|
||||||
|
|
||||||
|
ticket.setGranted(true);
|
||||||
|
|
||||||
|
authzClient.protection().permission().update(ticket);
|
||||||
|
|
||||||
|
assertTrue(hasPermission("kolo", "password", resource.getId(), "Scope A"));
|
||||||
|
|
||||||
|
resource.addScope("Scope B");
|
||||||
|
|
||||||
|
authorization.resources().resource(resource.getId()).update(resource);
|
||||||
|
|
||||||
|
assertTrue(hasPermission("kolo", "password", resource.getId()));
|
||||||
|
assertFalse(hasPermission("kolo", "password", resource.getId(), "Scope B"));
|
||||||
|
|
||||||
|
resource.setScopes(new HashSet<>());
|
||||||
|
|
||||||
|
authorization.resources().resource(resource.getId()).update(resource);
|
||||||
|
|
||||||
|
assertTrue(hasPermission("kolo", "password", resource.getId()));
|
||||||
|
assertFalse(hasPermission("kolo", "password", resource.getId(), "Scope A"));
|
||||||
|
}
|
||||||
|
|
||||||
public void testResourceServerAsAudience(String testClientId, String resourceServerClientId, String configFile) throws Exception {
|
public void testResourceServerAsAudience(String testClientId, String resourceServerClientId, String configFile) throws Exception {
|
||||||
AuthorizationRequest request = new AuthorizationRequest();
|
AuthorizationRequest request = new AuthorizationRequest();
|
||||||
|
|
||||||
|
@ -402,6 +510,29 @@ public class EntitlementAPITest extends AbstractAuthzTest {
|
||||||
assertEquals(resourceServerClientId, rpt.getAudience()[0]);
|
assertEquals(resourceServerClientId, rpt.getAudience()[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasPermission(String userName, String password, String resourceId, String... scopeIds) throws Exception {
|
||||||
|
String accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", userName, password).getAccessToken();
|
||||||
|
AuthorizationResponse response = getAuthzClient(AUTHZ_CLIENT_CONFIG).authorization(accessToken).authorize(new AuthorizationRequest());
|
||||||
|
AccessToken rpt = toAccessToken(response.getToken());
|
||||||
|
Authorization authz = rpt.getAuthorization();
|
||||||
|
List<Permission> permissions = authz.getPermissions();
|
||||||
|
|
||||||
|
assertNotNull(permissions);
|
||||||
|
assertFalse(permissions.isEmpty());
|
||||||
|
|
||||||
|
for (Permission grantedPermission : permissions) {
|
||||||
|
if (grantedPermission.getResourceId().equals(resourceId)) {
|
||||||
|
return scopeIds == null || scopeIds.length == 0 || grantedPermission.getScopes().containsAll(Arrays.asList(scopeIds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasPermission(String userName, String password, String resourceId) throws Exception {
|
||||||
|
return hasPermission(userName, password, resourceId, null);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertResponse(Metadata metadata, Supplier<AuthorizationResponse> responseSupplier) {
|
private void assertResponse(Metadata metadata, Supplier<AuthorizationResponse> responseSupplier) {
|
||||||
AccessToken.Authorization authorization = toAccessToken(responseSupplier.get().getToken()).getAuthorization();
|
AccessToken.Authorization authorization = toAccessToken(responseSupplier.get().getToken()).getAuthorization();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.testsuite.authz;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.admin.client.resource.AuthorizationResource;
|
||||||
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
|
import org.keycloak.admin.client.resource.ClientsResource;
|
||||||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
|
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
||||||
|
import org.keycloak.authorization.client.AuthzClient;
|
||||||
|
import org.keycloak.authorization.client.Configuration;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper;
|
||||||
|
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
|
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||||
|
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
||||||
|
import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
|
||||||
|
import org.keycloak.representations.idm.authorization.PermissionRequest;
|
||||||
|
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
|
||||||
|
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||||
|
import org.keycloak.testsuite.util.AdminClientUtil;
|
||||||
|
import org.keycloak.testsuite.util.ClientBuilder;
|
||||||
|
import org.keycloak.testsuite.util.GroupBuilder;
|
||||||
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
|
import org.keycloak.testsuite.util.RoleBuilder;
|
||||||
|
import org.keycloak.testsuite.util.RolesBuilder;
|
||||||
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public class GroupPathWithoutGroupClaimPolicyTest extends GroupPathPolicyTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||||
|
ProtocolMapperRepresentation groupProtocolMapper = new ProtocolMapperRepresentation();
|
||||||
|
|
||||||
|
groupProtocolMapper.setName("groups");
|
||||||
|
groupProtocolMapper.setProtocolMapper(GroupMembershipMapper.PROVIDER_ID);
|
||||||
|
groupProtocolMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
|
groupProtocolMapper.setConsentRequired(false);
|
||||||
|
Map<String, String> config = new HashMap<>();
|
||||||
|
config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "groups");
|
||||||
|
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
|
||||||
|
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
|
||||||
|
groupProtocolMapper.setConfig(config);
|
||||||
|
|
||||||
|
testRealms.add(RealmBuilder.create().name("authz-test")
|
||||||
|
.roles(RolesBuilder.create()
|
||||||
|
.realmRole(RoleBuilder.create().name("uma_authorization").build())
|
||||||
|
)
|
||||||
|
.group(GroupBuilder.create().name("Group A")
|
||||||
|
.subGroups(Arrays.asList("Group B", "Group D").stream().map(name -> {
|
||||||
|
if ("Group B".equals(name)) {
|
||||||
|
return GroupBuilder.create().name(name).subGroups(Arrays.asList("Group C", "Group E").stream().map(new Function<String, GroupRepresentation>() {
|
||||||
|
@Override
|
||||||
|
public GroupRepresentation apply(String name) {
|
||||||
|
return GroupBuilder.create().name(name).build();
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toList())).build();
|
||||||
|
}
|
||||||
|
return GroupBuilder.create().name(name).build();
|
||||||
|
}).collect(Collectors.toList())).build())
|
||||||
|
.group(GroupBuilder.create().name("Group E").build())
|
||||||
|
.user(UserBuilder.create().username("marta").password("password").addRoles("uma_authorization").addGroups("Group A"))
|
||||||
|
.user(UserBuilder.create().username("alice").password("password").addRoles("uma_authorization"))
|
||||||
|
.user(UserBuilder.create().username("kolo").password("password").addRoles("uma_authorization"))
|
||||||
|
.client(ClientBuilder.create().clientId("resource-server-test")
|
||||||
|
.secret("secret")
|
||||||
|
.authorizationServicesEnabled(true)
|
||||||
|
.redirectUris("http://localhost/resource-server-test")
|
||||||
|
.defaultRoles("uma_protection")
|
||||||
|
.directAccessGrants())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
|
@ -101,9 +101,20 @@ public class PermissionManagementTest extends AbstractResourceServerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteResourceAndPermissionTicket() throws Exception {
|
public void testDeleteResourceAndPermissionTicket() throws Exception {
|
||||||
ResourceRepresentation resource = addResource("Resource A", true);
|
ResourceRepresentation resource = addResource("Resource A", "kolo", true, "ScopeA", "ScopeB", "ScopeC");
|
||||||
PermissionResponse response = getAuthzClient().protection().permission().create(new PermissionRequest(resource.getName()));
|
AuthzClient authzClient = getAuthzClient();
|
||||||
assertNotNull(response.getTicket());
|
PermissionResponse response = authzClient.protection("marta", "password").permission().create(new PermissionRequest(resource.getId(), "ScopeA", "ScopeB", "ScopeC"));
|
||||||
|
AuthorizationRequest request = new AuthorizationRequest();
|
||||||
|
request.setTicket(response.getTicket());
|
||||||
|
request.setClaimToken(authzClient.obtainAccessToken("marta", "password").getToken());
|
||||||
|
|
||||||
|
try {
|
||||||
|
authzClient.authorization().authorize(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
assertPersistence(response, resource, "ScopeA", "ScopeB", "ScopeC");
|
||||||
|
|
||||||
getAuthzClient().protection().resource().delete(resource.getId());
|
getAuthzClient().protection().resource().delete(resource.getId());
|
||||||
assertTrue(getAuthzClient().protection().permission().findByResource(resource.getId()).isEmpty());
|
assertTrue(getAuthzClient().protection().permission().findByResource(resource.getId()).isEmpty());
|
||||||
|
|
|
@ -665,6 +665,6 @@ public class PolicyEvaluationTest extends AbstractAuthzTest {
|
||||||
}
|
}
|
||||||
return baseAttributes;
|
return baseAttributes;
|
||||||
}
|
}
|
||||||
}, policy, policy, evaluation -> {}, authorization);
|
}, policy, evaluation -> {}, authorization);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
|
||||||
JSPolicyRepresentation policy = new JSPolicyRepresentation();
|
JSPolicyRepresentation policy = new JSPolicyRepresentation();
|
||||||
|
|
||||||
policy.setName("Only Owner Policy");
|
policy.setName("Only Owner Policy");
|
||||||
policy.setCode("print($evaluation.getPermission().getResource().getOwner());print($evaluation.getContext().getIdentity().getId());if ($evaluation.getContext().getIdentity().getId() == $evaluation.getPermission().getResource().getOwner()) {$evaluation.grant();}");
|
policy.setCode("if ($evaluation.getContext().getIdentity().getId() == $evaluation.getPermission().getResource().getOwner()) {$evaluation.grant();}");
|
||||||
|
|
||||||
Response response = authorization.policies().js().create(policy);
|
Response response = authorization.policies().js().create(policy);
|
||||||
response.close();
|
response.close();
|
||||||
|
@ -98,6 +98,90 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure permissions granted to a typed resource instance does not grant access to resource instances with the same type.
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testOnlyOwnerCanAccessResourceWithType() throws Exception {
|
||||||
|
ResourceRepresentation typedResource = addResource("Typed Resource", getClient(getRealm()).toRepresentation().getId(), false, "ScopeA", "ScopeB");
|
||||||
|
|
||||||
|
typedResource.setType("my:resource");
|
||||||
|
|
||||||
|
getClient(getRealm()).authorization().resources().resource(typedResource.getId()).update(typedResource);
|
||||||
|
|
||||||
|
resource = addResource("Resource A", "marta", true, "ScopeA", "ScopeB");
|
||||||
|
|
||||||
|
resource.setType(typedResource.getType());
|
||||||
|
|
||||||
|
getClient(getRealm()).authorization().resources().resource(resource.getId()).update(resource);
|
||||||
|
|
||||||
|
ResourceRepresentation resourceB = addResource("Resource B", "marta", true, "ScopeA", "ScopeB");
|
||||||
|
|
||||||
|
resourceB.setType(typedResource.getType());
|
||||||
|
|
||||||
|
getClient(getRealm()).authorization().resources().resource(resourceB.getId()).update(resourceB);
|
||||||
|
|
||||||
|
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
||||||
|
|
||||||
|
permission.setName(resource.getType() + " Permission");
|
||||||
|
permission.setResourceType(resource.getType());
|
||||||
|
permission.addPolicy("Only Owner Policy");
|
||||||
|
|
||||||
|
getClient(getRealm()).authorization().permissions().resource().create(permission).close();
|
||||||
|
|
||||||
|
AuthorizationResponse response = authorize("marta", "password", resource.getName(), new String[] {"ScopeA", "ScopeB"});
|
||||||
|
String rpt = response.getToken();
|
||||||
|
|
||||||
|
assertNotNull(rpt);
|
||||||
|
assertFalse(response.isUpgraded());
|
||||||
|
|
||||||
|
AccessToken accessToken = toAccessToken(rpt);
|
||||||
|
AccessToken.Authorization authorization = accessToken.getAuthorization();
|
||||||
|
|
||||||
|
assertNotNull(authorization);
|
||||||
|
|
||||||
|
List<Permission> permissions = authorization.getPermissions();
|
||||||
|
|
||||||
|
assertNotNull(permissions);
|
||||||
|
assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB");
|
||||||
|
assertTrue(permissions.isEmpty());
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = authorize("kolo", "password", resource.getId(), new String[] {"ScopeA", "ScopeB"});
|
||||||
|
fail("User should not have access to resource from another user");
|
||||||
|
} catch (AuthorizationDeniedException ade) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PermissionTicketRepresentation> tickets = getAuthzClient().protection().permission().find(resource.getId(), null, null, null, null, null, null, null);
|
||||||
|
|
||||||
|
for (PermissionTicketRepresentation ticket : tickets) {
|
||||||
|
ticket.setGranted(true);
|
||||||
|
getAuthzClient().protection().permission().update(ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = authorize("kolo", "password", resource.getId(), new String[] {"ScopeA", "ScopeB"});
|
||||||
|
} catch (AuthorizationDeniedException ade) {
|
||||||
|
fail("User should have access to resource from another user");
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions = authorization.getPermissions();
|
||||||
|
|
||||||
|
assertNotNull(permissions);
|
||||||
|
assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB");
|
||||||
|
assertTrue(permissions.isEmpty());
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = authorize("kolo", "password", resourceB.getId(), new String[] {"ScopeA", "ScopeB"});
|
||||||
|
fail("User should not have access to resource from another user");
|
||||||
|
} catch (AuthorizationDeniedException ade) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUserGrantsAccessToResource() throws Exception {
|
public void testUserGrantsAccessToResource() throws Exception {
|
||||||
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
||||||
|
@ -306,7 +390,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
response = authorize("kolo", "password", resource.getId(), new String[] {"ScopeA"});
|
response = authorize("kolo", "password", resource.getId(), new String[] {"ScopeA"});
|
||||||
fail("User should have access to resource from another user");
|
fail("User should not have access to resource from another user");
|
||||||
} catch (AuthorizationDeniedException ade) {
|
} catch (AuthorizationDeniedException ade) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue