mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
Initial refactoring to make federated identities a condition
Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
17a053b2af
commit
03cbc11e7e
@ -26,15 +26,22 @@ import jakarta.persistence.criteria.Subquery;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
|
||||
import org.keycloak.models.jpa.entities.UserEntity;
|
||||
import org.keycloak.models.policy.conditions.IdentityProviderPolicyConditionFactory;
|
||||
import org.keycloak.models.policy.conditions.IdentityProviderPolicyConditionProvider;
|
||||
|
||||
public abstract class AbstractUserResourcePolicyProvider implements ResourcePolicyProvider {
|
||||
|
||||
@ -42,8 +49,6 @@ public abstract class AbstractUserResourcePolicyProvider implements ResourcePoli
|
||||
private final EntityManager em;
|
||||
private final KeycloakSession session;
|
||||
|
||||
private static final String BROKER_ALIASES = "broker-aliases";
|
||||
|
||||
public AbstractUserResourcePolicyProvider(KeycloakSession session, ComponentModel model) {
|
||||
this.policyModel = model;
|
||||
this.em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
||||
@ -187,6 +192,41 @@ public abstract class AbstractUserResourcePolicyProvider implements ResourcePoli
|
||||
}
|
||||
|
||||
protected List<String> getBrokerAliases() {
|
||||
return getModel().getConfig().getOrDefault(BROKER_ALIASES, List.of());
|
||||
List<String> conditions = policyModel.getConfig().getOrDefault("conditions", List.of());
|
||||
|
||||
for (String providerId : conditions) {
|
||||
ResourcePolicyConditionProvider condition = resolveCondition(providerId);
|
||||
|
||||
if (condition instanceof IdentityProviderPolicyConditionProvider) {
|
||||
return getModel().getConfig().getOrDefault(providerId + "." + IdentityProviderPolicyConditionFactory.EXPECTED_ALIASES, List.of());
|
||||
}
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private ResourcePolicyConditionProvider resolveCondition(String providerId) {
|
||||
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||
ResourcePolicyConditionProviderFactory<ResourcePolicyConditionProvider> providerFactory = (ResourcePolicyConditionProviderFactory<ResourcePolicyConditionProvider>) sessionFactory.getProviderFactory(ResourcePolicyConditionProvider.class, providerId);
|
||||
|
||||
if (providerFactory == null) {
|
||||
throw new IllegalStateException("Could not find condition provider: " + providerId);
|
||||
}
|
||||
|
||||
Map<String, List<String>> config = new HashMap<>();
|
||||
|
||||
for (Entry<String, List<String>> configEntry : policyModel.getConfig().entrySet()) {
|
||||
if (configEntry.getKey().startsWith(providerId)) {
|
||||
config.put(configEntry.getKey().substring(providerId.length() + 1), configEntry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
ResourcePolicyConditionProvider condition = providerFactory.create(session, config);
|
||||
|
||||
if (condition == null) {
|
||||
throw new IllegalStateException("Factory " + providerFactory.getClass() + " returned a null provider");
|
||||
}
|
||||
|
||||
return condition;
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ public class EventBasedResourcePolicyProvider implements ResourcePolicyProvider
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean scheduleOnEvent(ResourcePolicyEvent event) {
|
||||
public boolean activateOnEvent(ResourcePolicyEvent event) {
|
||||
List<String> events = model.getConfig().getOrDefault("events", List.of());
|
||||
ResourceOperationType operation = event.getOperation();
|
||||
|
||||
@ -51,6 +51,11 @@ public class EventBasedResourcePolicyProvider implements ResourcePolicyProvider
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deactivateOnEvent(ResourcePolicyEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetOnEvent(ResourcePolicyEvent event) {
|
||||
return false;
|
||||
@ -82,6 +87,7 @@ public class EventBasedResourcePolicyProvider implements ResourcePolicyProvider
|
||||
if (condition == null) {
|
||||
throw new IllegalStateException("Factory " + providerFactory.getClass() + " returned a null provider");
|
||||
}
|
||||
|
||||
return condition;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,21 @@
|
||||
package org.keycloak.models.policy;
|
||||
/*
|
||||
* Copyright 2025 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.models.policy.conditions;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.policy.ResourcePolicyConditionProviderFactory;
|
||||
|
||||
public class GroupMembershipPolicyConditionFactory implements ResourcePolicyConditionProviderFactory<GroupMembershipPolicyConditionProvider> {
|
||||
|
||||
@ -1,4 +1,14 @@
|
||||
package org.keycloak.models.policy;
|
||||
/*
|
||||
* Copyright 2025 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.models.policy.conditions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -6,8 +16,11 @@ import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.policy.ResourcePolicyConditionProvider;
|
||||
import org.keycloak.models.policy.ResourcePolicyEvent;
|
||||
import org.keycloak.models.policy.ResourceType;
|
||||
|
||||
public class GroupMembershipPolicyConditionProvider implements ResourcePolicyConditionProvider {
|
||||
public class GroupMembershipPolicyConditionProvider implements ResourcePolicyConditionProvider {
|
||||
|
||||
private final List<String> expectedGroups;
|
||||
private final KeycloakSession session;
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2025 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.models.policy.conditions;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.policy.ResourcePolicyConditionProviderFactory;
|
||||
|
||||
public class IdentityProviderPolicyConditionFactory implements ResourcePolicyConditionProviderFactory<IdentityProviderPolicyConditionProvider> {
|
||||
|
||||
public static final String ID = "identity-provider-condition";
|
||||
public static final String EXPECTED_ALIASES = "alias";
|
||||
|
||||
@Override
|
||||
public IdentityProviderPolicyConditionProvider create(KeycloakSession session, Map<String, List<String>> config) {
|
||||
return new IdentityProviderPolicyConditionProvider(session, config.get(EXPECTED_ALIASES));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(org.keycloak.Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2025 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.models.policy.conditions;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.policy.ResourcePolicyConditionProvider;
|
||||
import org.keycloak.models.policy.ResourcePolicyEvent;
|
||||
import org.keycloak.models.policy.ResourceType;
|
||||
|
||||
public class IdentityProviderPolicyConditionProvider implements ResourcePolicyConditionProvider {
|
||||
|
||||
private final List<String> expectedAliases;
|
||||
private final KeycloakSession session;
|
||||
|
||||
public IdentityProviderPolicyConditionProvider(KeycloakSession session, List<String> expectedAliases) {
|
||||
this.session = session;
|
||||
this.expectedAliases = expectedAliases;;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(ResourcePolicyEvent event) {
|
||||
if (!ResourceType.USERS.equals(event.getResourceType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String userId = event.getResourceId();
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
UserModel user = session.users().getUserById(realm, userId);
|
||||
Stream<FederatedIdentityModel> federatedIdentities = session.users().getFederatedIdentitiesStream(realm, user);
|
||||
|
||||
return federatedIdentities
|
||||
.map(FederatedIdentityModel::getIdentityProvider)
|
||||
.anyMatch(expectedAliases::contains);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -15,4 +15,5 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
org.keycloak.models.policy.GroupMembershipPolicyConditionFactory
|
||||
org.keycloak.models.policy.conditions.GroupMembershipPolicyConditionFactory
|
||||
org.keycloak.models.policy.conditions.IdentityProviderPolicyConditionFactory
|
||||
@ -1,9 +1,13 @@
|
||||
package org.keycloak.models.policy;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.FederatedIdentityModel.FederatedIdentityCreatedEvent;
|
||||
import org.keycloak.models.FederatedIdentityModel.FederatedIdentityRemovedEvent;
|
||||
import org.keycloak.models.GroupModel.GroupMemberJoinEvent;
|
||||
import org.keycloak.provider.ProviderEvent;
|
||||
|
||||
@ -11,8 +15,8 @@ public enum ResourceOperationType {
|
||||
|
||||
CREATE(OperationType.CREATE, EventType.REGISTER),
|
||||
LOGIN(EventType.LOGIN),
|
||||
ADD_FEDERATED_IDENTITY,
|
||||
REMOVE_FEDERATED_IDENTITY,
|
||||
ADD_FEDERATED_IDENTITY(FederatedIdentityCreatedEvent.class),
|
||||
REMOVE_FEDERATED_IDENTITY(FederatedIdentityRemovedEvent.class),
|
||||
GROUP_MEMBERSHIP_JOIN(GroupMemberJoinEvent.class);
|
||||
|
||||
private final List<Object> types;
|
||||
@ -41,6 +45,20 @@ public enum ResourceOperationType {
|
||||
}
|
||||
for (Object type : value.types) {
|
||||
if (type instanceof Class<?> cls && cls.isAssignableFrom((Class<?>) from)) {
|
||||
// factory.register(fired -> {
|
||||
// ResourcePolicyEvent rpe = null;
|
||||
// if (fired instanceof FederatedIdentityModel.FederatedIdentityCreatedEvent event) {
|
||||
// rpe = new ResourcePolicyEvent(ResourceType.USERS, ResourceOperationType.ADD_FEDERATED_IDENTITY,
|
||||
// event.getUser().getId(), Map.of("provider", event.getFederatedIdentity().getIdentityProvider()));
|
||||
// ResourcePolicyManager manager = new ResourcePolicyManager(event.getKeycloakSession());
|
||||
// manager.processEvent(rpe);
|
||||
// } else if (fired instanceof FederatedIdentityModel.FederatedIdentityRemovedEvent event) {
|
||||
// rpe = new ResourcePolicyEvent(ResourceType.USERS, ResourceOperationType.REMOVE_FEDERATED_IDENTITY,
|
||||
// event.getUser().getId(), Map.of("provider", event.getFederatedIdentity().getIdentityProvider()));
|
||||
// ResourcePolicyManager manager = new ResourcePolicyManager(event.getKeycloakSession());
|
||||
// manager.processEvent(rpe);
|
||||
// }
|
||||
// });
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@ -52,6 +70,12 @@ public enum ResourceOperationType {
|
||||
if (event instanceof GroupMemberJoinEvent gme) {
|
||||
return gme.getUser().getId();
|
||||
}
|
||||
if (event instanceof FederatedIdentityModel.FederatedIdentityCreatedEvent fie) {
|
||||
return fie.getUser().getId();
|
||||
}
|
||||
if (event instanceof FederatedIdentityModel.FederatedIdentityRemovedEvent fie) {
|
||||
return fie.getUser().getId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,26 +1,15 @@
|
||||
package org.keycloak.models.policy;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ResourcePolicyEvent {
|
||||
|
||||
private final ResourceType type;
|
||||
private final ResourceOperationType operation;
|
||||
private final String resourceId;
|
||||
private final Map<String, String> details = new HashMap<>();
|
||||
|
||||
public ResourcePolicyEvent(ResourceType type, ResourceOperationType operation, String resourceId) {
|
||||
this(type, operation, resourceId, null);
|
||||
}
|
||||
|
||||
public ResourcePolicyEvent(ResourceType type, ResourceOperationType operation, String resourceId, Map<String, String> details) {
|
||||
this.type = type;
|
||||
this.operation = operation;
|
||||
this.resourceId = resourceId;
|
||||
if (details != null) {
|
||||
this.details.putAll(details);
|
||||
}
|
||||
}
|
||||
|
||||
public ResourceType getResourceType() {
|
||||
@ -34,8 +23,4 @@ public class ResourcePolicyEvent {
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return details;
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,10 +38,12 @@ import org.keycloak.models.policy.ResourcePolicy;
|
||||
import org.keycloak.models.policy.ResourcePolicyManager;
|
||||
import org.keycloak.models.policy.ResourcePolicyStateProvider;
|
||||
import org.keycloak.models.policy.UserSessionRefreshTimeResourcePolicyProviderFactory;
|
||||
import org.keycloak.models.policy.conditions.IdentityProviderPolicyConditionFactory;
|
||||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyActionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyConditionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyRepresentation;
|
||||
import org.keycloak.testframework.annotations.InjectClient;
|
||||
import org.keycloak.testframework.annotations.InjectRealm;
|
||||
@ -117,7 +119,10 @@ public class BrokeredUserSessionRefreshTimePolicyTest {
|
||||
public void tesRunActionOnFederatedUser() {
|
||||
consumerRealm.admin().resources().policies().create(ResourcePolicyRepresentation.create()
|
||||
.of(UserSessionRefreshTimeResourcePolicyProviderFactory.ID)
|
||||
.withConfig("broker-aliases", IDP_OIDC_ALIAS)
|
||||
.onCoditions(ResourcePolicyConditionRepresentation.create()
|
||||
.of(IdentityProviderPolicyConditionFactory.ID)
|
||||
.withConfig(IdentityProviderPolicyConditionFactory.EXPECTED_ALIASES, IDP_OIDC_ALIAS)
|
||||
.build())
|
||||
.withActions(
|
||||
ResourcePolicyActionRepresentation.create().of(DeleteUserActionProviderFactory.ID)
|
||||
.after(Duration.ofDays(1))
|
||||
@ -184,7 +189,10 @@ public class BrokeredUserSessionRefreshTimePolicyTest {
|
||||
public void testAddRemoveFedIdentityAffectsPolicyAssociation() {
|
||||
consumerRealm.admin().resources().policies().create(ResourcePolicyRepresentation.create()
|
||||
.of(UserSessionRefreshTimeResourcePolicyProviderFactory.ID)
|
||||
.withConfig("broker-aliases", IDP_OIDC_ALIAS)
|
||||
.onCoditions(ResourcePolicyConditionRepresentation.create()
|
||||
.of(IdentityProviderPolicyConditionFactory.ID)
|
||||
.withConfig(IdentityProviderPolicyConditionFactory.EXPECTED_ALIASES, IDP_OIDC_ALIAS)
|
||||
.build())
|
||||
.withActions(
|
||||
ResourcePolicyActionRepresentation.create().of(DeleteUserActionProviderFactory.ID)
|
||||
.after(Duration.ofDays(1))
|
||||
|
||||
@ -34,7 +34,7 @@ import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.policy.EventBasedResourcePolicyProviderFactory;
|
||||
import org.keycloak.models.policy.GroupMembershipPolicyConditionFactory;
|
||||
import org.keycloak.models.policy.conditions.GroupMembershipPolicyConditionFactory;
|
||||
import org.keycloak.models.policy.NotifyUserActionProviderFactory;
|
||||
import org.keycloak.models.policy.ResourceOperationType;
|
||||
import org.keycloak.models.policy.ResourcePolicyManager;
|
||||
@ -110,7 +110,7 @@ public class GroupMembershipJoinPolicyTest {
|
||||
try {
|
||||
// set offset to 7 days - notify action should run now
|
||||
Time.setOffset(Math.toIntExact(Duration.ofDays(6).toSeconds()));
|
||||
manager.runScheduledTasks();
|
||||
manager.runScheduledActions();
|
||||
user = session.users().getUserById(realm, userId);
|
||||
assertNotNull(user.getAttributes().get("message"));
|
||||
} finally {
|
||||
|
||||
@ -45,7 +45,9 @@ import org.keycloak.models.policy.ResourcePolicyManager;
|
||||
import org.keycloak.models.policy.ResourcePolicyStateProvider;
|
||||
import org.keycloak.models.policy.UserCreationTimeResourcePolicyProviderFactory;
|
||||
import org.keycloak.models.policy.UserSessionRefreshTimeResourcePolicyProviderFactory;
|
||||
import org.keycloak.models.policy.conditions.IdentityProviderPolicyConditionFactory;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyActionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyConditionRepresentation;
|
||||
import org.keycloak.representations.resources.policies.ResourcePolicyRepresentation;
|
||||
import org.keycloak.testframework.annotations.InjectRealm;
|
||||
import org.keycloak.testframework.annotations.InjectUser;
|
||||
@ -258,7 +260,10 @@ public class ResourcePolicyManagementTest {
|
||||
|
||||
managedRealm.admin().resources().policies().create(ResourcePolicyRepresentation.create()
|
||||
.of(UserCreationTimeResourcePolicyProviderFactory.ID)
|
||||
.withConfig("broker-aliases", "someidp")
|
||||
.onCoditions(ResourcePolicyConditionRepresentation.create()
|
||||
.of(IdentityProviderPolicyConditionFactory.ID)
|
||||
.withConfig(IdentityProviderPolicyConditionFactory.EXPECTED_ALIASES, "someidp")
|
||||
.build())
|
||||
.withActions(
|
||||
ResourcePolicyActionRepresentation.create().of(NotifyUserActionProviderFactory.ID)
|
||||
.after(Duration.ofDays(5))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user