8000 Manage Organization Roles by SferaDev · Pull Request #40586 · keycloak/keycloak · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Manage Organization Roles #40586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
< 8000 summary class="Link--muted select-menu-button" aria-haspopup="true" data-target="file-filter.summary"> File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright 2024 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.organizationrole;

import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Stream;

import org.jboss.logging.Logger;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.attribute.Attributes.Entry;
import org.keycloak.authorization.identity.Identity;
import org.keycloak.authorization.identity.UserModelIdentity;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.evaluation.Evaluation;
import org.keycloak.authorization.fgap.evaluation.partial.PartialEvaluationPolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.OrganizationRoleModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.organization.OrganizationProvider;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.idm.authorization.ResourceType;
import org.keycloak.representations.idm.authorization.OrganizationRolePolicyRepresentation;

/**
* Policy provider for organization role-based authorization.
*
* This provider evaluates authorization policies based on organization roles assigned to users.
1E0A * It extends the standard role-based policy evaluation to include organization-scoped roles.
* Organization roles provide fine-grained access control within organization boundaries.
*/
public class OrganizationRolePolicyProvider implements PolicyProvider, PartialEvaluationPolicyProvider {

private final BiFunction<Policy, AuthorizationProvider, OrganizationRolePolicyRepresentation> representationFunction;

private static final Logger logger = Logger.getLogger(OrganizationRolePolicyProvider.class);

public OrganizationRolePolicyProvider(BiFunction<Policy, AuthorizationProvider, OrganizationRolePolicyRepresentation> representationFunction) {
this.representationFunction = representationFunction;
}

@Override
public void evaluate(Evaluation evaluation) {
Policy policy = evaluation.getPolicy();
OrganizationRolePolicyRepresentation policyRep = representationFunction.apply(policy, evaluation.getAuthorizationProvider());
AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider();
RealmModel realm = authorizationProvider.getKeycloakSession().getContext().getRealm();
Identity identity = evaluation.getContext().getIdentity();

if (isGranted(realm, authorizationProvider, policyRep, identity)) {
evaluation.grant();
}

if (logger.isDebugEnabled()) {
logger.debugf("Organization role policy %s evaluated with status %s for identity %s",
policy.getName(), evaluation.getEffect(), identity.getId());
}
}

private boolean isGranted(RealmModel realm, AuthorizationProvider authorizationProvider,
OrganizationRolePolicyRepresentation policyRep, Identity identity) {

Set<OrganizationRolePolicyRepresentation.OrganizationRoleDefinition> orgRoles = policyRep.getOrganizationRoles();
boolean granted = false;

UserModel user = getSubject(identity, realm, authorizationProvider);
if (user == null) {
return false;
}

OrganizationProvider orgProvider = authorizationProvider.getKeycloakSession().getProvider(OrganizationProvider.class);

for (OrganizationRolePolicyRepresentation.OrganizationRoleDefinition roleDefinition : orgRoles) {
String organizationId = roleDefinition.getOrganizationId();
String roleId = roleDefinition.getRoleId();

OrganizationModel organization = orgProvider.getById(organizationId);
if (organization == null) {
continue;
}

OrganizationRoleModel role = organization.getRoleById(roleId);
if (role == null) {
continue;
}

boolean hasRole = organization.hasRole(user, role);

if (!hasRole && roleDefinition.isRequired() != null && roleDefinition.isRequired()) {
return false;
} else if (hasRole) {
granted = true;
}
}

return granted;
}

private UserModel getSubject(Identity identity, RealmModel realm, AuthorizationProvider authorizationProvider) {
KeycloakSession session = authorizationProvider.getKeycloakSession();
UserProvider users = session.users();
UserModel user = users.getUserById(realm, identity.getId());

if (user == null) {
Entry sub = identity.getAttributes().getValue(JsonWebToken.SUBJECT);

if (sub == null || sub.isEmpty()) {
return null;
}

return users.getUserById(realm, sub.asString(0));
}

return user;
}

@Override
public void close() {
// No resources to close
}

@Override
public Stream<Policy> getPermissions(KeycloakSession session, ResourceType resourceType, UserModel subject) {
AuthorizationProvider provider = session.getProvider(AuthorizationProvider.class);
RealmModel realm = session.getContext().getRealm();
StoreFactory storeFactory = provider.getStoreFactory();
PolicyStore policyStore = storeFactory.getPolicyStore();

// Get all organizations the user is a member of and their roles
OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class);
Stream<String> organizationRoleIds = orgProvider.getAllStream()
.filter(org -> orgProvider.isMember(org, subject))
.flatMap(org -> org.getUserRolesStream(subject))
.map(OrganizationRoleModel::getId);

List<String> roleIds = organizationRoleIds.toList();

// Find policies that depend on these organization roles
return policyStore.findDependentPolicies(
storeFactory.getResourceServerStore().findByClient(realm.getAdminPermissionsClient()),
resourceType.getType(),
OrganizationRolePolicyProviderFactory.ID,
"organizationRoles",
roleIds);
}

@Override
public boolean evaluate(KeycloakSession session, Policy policy, UserModel adminUser) {
RealmModel realm = session.getContext().getRealm();
AuthorizationProvider authorizationProvider = session.getProvider(AuthorizationProvider.class);
return isGranted(realm, authorizationProvider,
representationFunction.apply(policy, authorizationProvider),
new UserModelIdentity(realm, adminUser));
}

@Override
public boolean supports(Policy policy) {
return OrganizationRolePolicyProviderFactory.ID.equals(policy.getType());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/*
* Copyright 2024 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.organizationrole;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

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.models.OrganizationModel;
import org.keycloak.models.OrganizationRoleModel;
import org.keycloak.organization.OrganizationProvider;
import org.keycloak.representations.idm.authorization.OrganizationRolePolicyRepresentation;
import org.keycloak.representations.idm.authorization.OrganizationRolePolicyRepresentation.OrganizationRoleDefinition;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.util.JsonSerialization;

/**
* Factory for creating organization role-based policy providers.
*
* This factory handles the creation and configuration of policies that
* evaluate authorization based on organization roles.
*/
public class OrganizationRolePolicyProviderFactory implements PolicyProviderFactory<OrganizationRolePolicyRepresentation> {

public static final String ID = "organization-role";

private OrganizationRolePolicyProvider provider = new OrganizationRolePolicyProvider(this::toRepresentation);

@Override
public String getName() {
return "Organization Role";
}

@Override
public String getGroup() {
return "Identity Based";
}

@Override
public PolicyProvider create(AuthorizationProvider authorization) {
return provider;
}

@Override
public PolicyProvider create(KeycloakSession session) {
return provider;
}

@Override
public OrganizationRolePolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
OrganizationRolePolicyRepresentation representation = new OrganizationRolePolicyRepresentation();
String organizationRoles = policy.getConfig().get("organizationRoles");

if (organizationRoles != null) {
try {
@SuppressWarnings("unchecked")
Set<OrganizationRoleDefinition> roleDefinitions = JsonSerialization.readValue(organizationRoles.getBytes(), Set.class);
representation.setOrganizationRoles(roleDefinitions);
} catch (IOException cause) {
throw new RuntimeException("Failed to deserialize organization roles for policy [" + policy.getName() + "]", cause);
}
}

String fetchRoles = policy.getConfig().get("fetchRoles");
if (fetchRoles != null) {
representation.setFetchRoles(Boolean.parseBoolean(fetchRoles));
}

representation.setId(policy.getId());
representation.setName(policy.getName());
representation.setDescription(policy.getDescription());
representation.setDecisionStrategy(policy.getDecisionStrategy());
representation.setLogic(policy.getLogic());

return representation;
}

@Override
public Class<OrganizationRolePolicyRepresentation> getRepresentationType() {
return OrganizationRolePolicyRepresentation.class;
}

@Override
public void onCreate(Policy policy, OrganizationRolePolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation, authorization);
}

@Override
public void onUpdate(Policy policy, OrganizationRolePolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation, authorization);
}

@Override
public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
String organizationRoles = representation.getConfig().get("organizationRoles");
if (organizationRoles != null) {
policy.putConfig("organizationRoles", organizationRoles);
}

String fetchRoles = representation.getConfig().get("fetchRoles");
if (fetchRoles != null) {
policy.putConfig("fetchRoles", fetchRoles);
}
}

@Override
public void onExport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorizationProvider) {
Map<String, String> config = new HashMap<>();

String organizationRoles = policy.getConfig().get("organizationRoles");
if (organizationRoles != null) {
config.put("organizationRoles", organizationRoles);
}

String fetchRoles = policy.getConfig().get("fetchRoles");
if (fetchRoles != null) {
config.put("fetchRoles", fetchRoles);
}

representation.setConfig(config);
}

private void updatePolicy(Policy policy, OrganizationRolePolicyRepresentation representation, AuthorizationProvider authorization) {
Set<OrganizationRoleDefinition> organizationRoles = representation.getOrganizationRoles();

if (organizationRoles != null) {
validateOrganizationRoles(organizationRoles, authorization);

try {
policy.putConfig("organizationRoles", JsonSerialization.writeValueAsString(organizationRoles));
} catch (IOException cause) {
throw new RuntimeException("Failed to serialize organization roles for policy [" + policy.getName() + "]", cause);
}
}

if (representation.isFetchRoles() != null) {
policy.putConfig("fetchRoles", String.valueOf(representation.isFetchRoles()));
}
}

private void validateOrganizationRoles(Set<OrganizationRoleDefinition> organizationRoles, AuthorizationProvider authorization) {
KeycloakSession session = authorization.getKeycloakSession();
OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class);

for (OrganizationRoleDefinition roleDefinition : organizationRoles) {
String organizationId = roleDefinition.getOrganizationId();
String roleId = roleDefinition.getRoleId();

if (organizationId == null || roleId == null) {
throw new RuntimeException("Organization ID and Role ID are required for organization role policy");
}

OrganizationModel organization = orgProvider.getById(organizationId);
if (organization == null) {
throw new RuntimeException("Organization with ID '" + organizationId + "' not found");
}

OrganizationRoleModel role = organization.getRoleById(roleId);
if (role == null) {
throw new RuntimeException("Role with ID '" + roleId + "' not found in organization '" + organization.getName() + "'");
}
}
}

@Override
public void init(Config.Scope config) {
// No initialization required
}

@Override
public void postInit(KeycloakSessionFactory factory) {
// No post-initialization required
}

@Override
public void close() {
// No resources to close
}

@Override
public String getId() {
return ID;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ org.keycloak.authorization.policy.provider.aggregated.AggregatePolicyProviderFac
org.keycloak.authorization.policy.provider.js.JSPolicyProviderFactory
org.keycloak.authorization.policy.provider.permission.ResourcePolicyProviderFactory
org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory
org.keycloak.authorization.policy.provider.organizationrole.OrganizationRolePolicyProviderFactory
org.keycloak.authorization.policy.provider.permission.ScopePolicyProviderFactory
org.keycloak.authorization.policy.provider.time.TimePolicyProviderFactory
org.keycloak.authorization.policy.provider.user.UserPolicyProviderFactory
Expand Down
Loading
Loading
0