Make PermissionTicket events marshallable

Fixes #35328

Signed-off-by: Pedro Ruivo <pruivo@redhat.com>
This commit is contained in:
Pedro Ruivo 2024-11-27 10:30:06 +00:00 committed by Alexander Schwartz
parent fda6dde44f
commit a65fd34bbf
6 changed files with 355 additions and 80 deletions

View File

@ -36,6 +36,8 @@ import org.keycloak.component.ComponentModel;
import org.keycloak.keys.infinispan.PublicKeyStorageInvalidationEvent;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.cache.infinispan.ClearCacheEvent;
import org.keycloak.models.cache.infinispan.authorization.events.PermissionTicketRemovedEvent;
import org.keycloak.models.cache.infinispan.authorization.events.PermissionTicketUpdatedEvent;
import org.keycloak.models.cache.infinispan.authorization.events.PolicyRemovedEvent;
import org.keycloak.models.cache.infinispan.authorization.events.PolicyUpdatedEvent;
import org.keycloak.models.cache.infinispan.authorization.events.ResourceRemovedEvent;
@ -136,6 +138,8 @@ import org.keycloak.storage.managers.UserStorageSyncManager;
ClearCacheEvent.class,
//models.cache.infinispan.authorization.events package
PermissionTicketRemovedEvent.class,
PermissionTicketUpdatedEvent.class,
PolicyUpdatedEvent.class,
PolicyRemovedEvent.class,
ResourceUpdatedEvent.class,

View File

@ -160,6 +160,9 @@ public final class Marshalling {
public static final int MAP_ENTRY_TO_KEY_FUNCTION = 65611;
public static final int SESSION_UNWRAP_MAPPER = 65612;
public static final int PERMISSION_TICKET_REMOVED_EVENT = 65613;
public static final int PERMISSION_TICKET_UPDATED_EVENT = 65614;
public static void configure(GlobalConfigurationBuilder builder) {
builder.serialization()
.addContextInitializer(KeycloakModelSchema.INSTANCE);

View File

@ -0,0 +1,93 @@
/*
* 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.models.cache.infinispan.authorization.events;
import java.util.Objects;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
abstract class BasePermissionTicketEvent extends InvalidationEvent implements AuthorizationCacheInvalidationEvent {
private final String owner;
private final String resource;
private final String scope;
private final String serverId;
private final String requester;
private final String resourceName;
BasePermissionTicketEvent(String id, String owner, String resource, String scope, String serverId, String requester, String resourceName) {
super(id);
this.owner = owner;
this.resource = resource;
this.scope = scope;
this.serverId = serverId;
this.requester = requester;
this.resourceName = resourceName;
}
@ProtoField(2)
public String getOwner() {
return owner;
}
@ProtoField(3)
public String getRequester() {
return requester;
}
@ProtoField(4)
public String getResource() {
return resource;
}
@ProtoField(5)
public String getResourceName() {
return resourceName;
}
@ProtoField(6)
public String getScope() {
return scope;
}
@ProtoField(7)
public String getServerId() {
return serverId;
}
@Override
public String toString() {
return "%s [ id=%s, name=%s]".formatted(getClass().getName(), getId(), resource);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
BasePermissionTicketEvent that = (BasePermissionTicketEvent) o;
return Objects.equals(resource, that.resource) && Objects.equals(serverId, that.serverId);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), resource, serverId);
}
}

View File

@ -17,60 +17,30 @@
package org.keycloak.models.cache.infinispan.authorization.events;
import java.util.Objects;
import java.util.Set;
import org.infinispan.protostream.annotations.ProtoFactory;
import org.infinispan.protostream.annotations.ProtoTypeId;
import org.keycloak.marshalling.Marshalling;
import org.keycloak.models.cache.infinispan.authorization.StoreFactoryCacheManager;
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PermissionTicketRemovedEvent extends InvalidationEvent implements AuthorizationCacheInvalidationEvent {
@ProtoTypeId(Marshalling.PERMISSION_TICKET_REMOVED_EVENT)
public class PermissionTicketRemovedEvent extends BasePermissionTicketEvent {
private String owner;
private String resource;
private String scope;
private String serverId;
private String requester;
private String resourceName;
private PermissionTicketRemovedEvent(String id) {
super(id);
@ProtoFactory
PermissionTicketRemovedEvent(String id, String owner, String resource, String scope, String serverId, String requester, String resourceName) {
super(id, owner, resource, scope, serverId, requester, resourceName);
}
public static PermissionTicketRemovedEvent create(String id, String owner, String requester, String resource, String resourceName, String scope, String serverId) {
PermissionTicketRemovedEvent event = new PermissionTicketRemovedEvent(id);
event.owner = owner;
event.requester = requester;
event.resource = resource;
event.resourceName = resourceName;
event.scope = scope;
event.serverId = serverId;
return event;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
PermissionTicketRemovedEvent that = (PermissionTicketRemovedEvent) o;
return Objects.equals(resource, that.resource) && Objects.equals(serverId, that.serverId);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), resource, serverId);
}
@Override
public String toString() {
return String.format("PermissionTicketRemovedEvent [ id=%s, name=%s]", getId(), resource);
return new PermissionTicketRemovedEvent(id, owner, resource, scope, serverId, requester, resourceName);
}
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
cache.permissionTicketRemoval(getId(), owner, requester, resource, resourceName, scope, serverId, invalidations);
cache.permissionTicketRemoval(getId(), getOwner(), getRequester(), getResource(), getResourceName(), getScope(), getServerId(), invalidations);
}
}

View File

@ -17,60 +17,30 @@
package org.keycloak.models.cache.infinispan.authorization.events;
import java.util.Objects;
import java.util.Set;
import org.infinispan.protostream.annotations.ProtoFactory;
import org.infinispan.protostream.annotations.ProtoTypeId;
import org.keycloak.marshalling.Marshalling;
import org.keycloak.models.cache.infinispan.authorization.StoreFactoryCacheManager;
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PermissionTicketUpdatedEvent extends InvalidationEvent implements AuthorizationCacheInvalidationEvent {
@ProtoTypeId(Marshalling.PERMISSION_TICKET_UPDATED_EVENT)
public class PermissionTicketUpdatedEvent extends BasePermissionTicketEvent {
private String owner;
private String resource;
private String scope;
private String serverId;
private String requester;
private String resourceName;
private PermissionTicketUpdatedEvent(String id) {
super(id);
@ProtoFactory
PermissionTicketUpdatedEvent(String id, String owner, String resource, String scope, String serverId, String requester, String resourceName) {
super(id, owner, resource, scope, serverId, requester, resourceName);
}
public static PermissionTicketUpdatedEvent create(String id, String owner, String requester, String resource, String resourceName, String scope, String serverId) {
PermissionTicketUpdatedEvent event = new PermissionTicketUpdatedEvent(id);
event.owner = owner;
event.requester = requester;
event.resource = resource;
event.resourceName = resourceName;
event.scope = scope;
event.serverId = serverId;
return event;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
PermissionTicketUpdatedEvent that = (PermissionTicketUpdatedEvent) o;
return Objects.equals(resource, that.resource) && Objects.equals(serverId, that.serverId);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), resource, serverId);
}
@Override
public String toString() {
return String.format("PermissionTicketUpdatedEvent [ id=%s, name=%s]", getId(), resource);
return new PermissionTicketUpdatedEvent(id, owner, resource, scope, serverId, requester, resourceName);
}
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
cache.permissionTicketUpdated(getId(), owner, requester, resource, resourceName, scope, serverId, invalidations);
cache.permissionTicketUpdated(getId(), getOwner(), getRequester(), getResource(), getResourceName(), getScope(), getServerId(), invalidations);
}
}

View File

@ -0,0 +1,235 @@
/*
* 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.testsuite.cluster;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.commons.lang.RandomStringUtils;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.AuthorizationProviderFactory;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.authorization.PermissionTicketRepresentation;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.ContainerInfo;
import static org.junit.Assert.assertEquals;
public class PermissionTicketInvalidationClusterTest extends AbstractInvalidationClusterTestWithTestRealm<PermissionTicketRepresentation, Object> {
private String clientId;
private String userId;
private String resourceId;
private String scopeId;
private final String resourceName = RandomStringUtils.randomAlphabetic(5);
private final String scopeName = RandomStringUtils.randomAlphabetic(5);
@Override
protected void createTestRealm(ContainerInfo node) {
super.createTestRealm(node);
createClient(node);
createUser(node);
createResource(node);
createScope(node);
}
private void createClient(ContainerInfo node) {
var client = new ClientRepresentation();
String s = RandomStringUtils.randomAlphabetic(5);
client.setClientId("client_" + s);
client.setName("name_" + s);
try (var rsp = getAdminClientFor(node)
.realm(testRealmName)
.clients()
.create(client)) {
clientId = ApiUtil.getCreatedId(rsp);
}
}
private void createUser(ContainerInfo node) {
var user = createUserRepresentation("user1", "password");
try (var rsp = getAdminClientFor(node)
.realm(testRealmName)
.users()
.create(user)) {
userId = ApiUtil.getCreatedId(rsp);
}
}
private void createResource(ContainerInfo node) {
var realmFinal = testRealmName;
var clientFinal = clientId;
var resourceFinal = resourceName;
resourceId = getTestingClientFor(node).server().fetchString(session -> {
var realm = session.realms().getRealmByName(realmFinal);
session.getContext().setRealm(realm);
var factory = (AuthorizationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class);
var storeFactory = factory.create(session, realm).getStoreFactory();
var client = session.clients().getClientById(realm, clientFinal);
var resourceServer = storeFactory.getResourceServerStore().findByClient(client);
if (resourceServer == null) {
resourceServer = storeFactory.getResourceServerStore().create(client);
}
return storeFactory.getResourceStore().create(resourceServer, resourceFinal, clientFinal).getId();
}).replaceAll("\"", "");
}
private void createScope(ContainerInfo node) {
var realmFinal = testRealmName;
var clientFinal = clientId;
var scopeFinal = scopeName;
scopeId = getTestingClientFor(node).server().fetchString(session -> {
var realm = session.realms().getRealmByName(realmFinal);
session.getContext().setRealm(realm);
var storeFactory = session.getProvider(AuthorizationProvider.class).getStoreFactory();
var client = session.clients().getClientById(realm, clientFinal);
var resourceServer = storeFactory.getResourceServerStore().findByClient(client);
if (resourceServer == null) {
resourceServer = storeFactory.getResourceServerStore().create(client);
}
return storeFactory.getScopeStore().create(resourceServer, scopeFinal).getId();
}).replaceAll("\"", "");
}
@Override
protected PermissionTicketRepresentation createTestEntityRepresentation() {
var ticket = new PermissionTicketRepresentation();
ticket.setGranted(true);
ticket.setOwner(clientId); // client is the owner
ticket.setRequester(userId); // userid is the requester
ticket.setScope(scopeId);
ticket.setResource(resourceId);
return ticket;
}
@Override
protected Object entityResource(PermissionTicketRepresentation testEntity, ContainerInfo node) {
throw new UnsupportedOperationException();
}
@Override
protected Object entityResource(String idOrName, ContainerInfo node) {
throw new UnsupportedOperationException();
}
@Override
protected PermissionTicketRepresentation createEntity(PermissionTicketRepresentation testEntity, ContainerInfo node) {
var realmFinal = testRealmName;
var clientFinal = clientId;
var scopeFinal = scopeId;
var resourceFinal = resourceId;
var userFinal = userId;
return getTestingClientFor(node).server().fetch(session -> {
var realm = session.realms().getRealmByName(realmFinal);
session.getContext().setRealm(realm);
var factory = (AuthorizationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class);
var provider = factory.create(session, realm);
var storeFactory = provider.getStoreFactory();
var client = session.clients().getClientById(realm, clientFinal);
var resourceServer = storeFactory.getResourceServerStore().findByClient(client);
var resource = storeFactory.getResourceStore().findById(resourceServer, resourceFinal);
var scope = storeFactory.getScopeStore().findById(resourceServer, scopeFinal);
var ticket = storeFactory.getPermissionTicketStore().create(resourceServer, resource, scope, userFinal);
return ModelToRepresentation.toRepresentation(ticket, provider, false);
}, PermissionTicketRepresentation.class);
}
@Override
protected PermissionTicketRepresentation readEntity(PermissionTicketRepresentation entity, ContainerInfo node) {
var realmFinal = testRealmName;
var clientFinal = clientId;
var idFinal = entity.getId();
return getTestingClientFor(node).server().fetch(session -> {
var realm = session.realms().getRealmByName(realmFinal);
session.getContext().setRealm(realm);
var factory = (AuthorizationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class);
var provider = factory.create(session, realm);
var storeFactory = provider.getStoreFactory();
var client = session.clients().getClientById(realm, clientFinal);
var resourceServer = storeFactory.getResourceServerStore().findByClient(client);
var ticket = storeFactory.getPermissionTicketStore().findById(resourceServer, idFinal);
return ticket == null ? null : ModelToRepresentation.toRepresentation(ticket, provider, false);
}, PermissionTicketRepresentation.class);
}
@Override
protected PermissionTicketRepresentation updateEntity(PermissionTicketRepresentation entity, ContainerInfo node) {
throw new UnsupportedOperationException();
}
@Override
protected void deleteEntity(PermissionTicketRepresentation entity, ContainerInfo node) {
var realmFinal = testRealmName;
var idFinal = entity.getId();
getTestingClientFor(node).server().run(session -> {
var realm = session.realms().getRealmByName(realmFinal);
session.getContext().setRealm(realm);
var factory = (AuthorizationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class);
var storeFactory = factory.create(session, realm).getStoreFactory();
storeFactory.getPermissionTicketStore().delete(idFinal);
});
}
@Override
protected PermissionTicketRepresentation testEntityUpdates(PermissionTicketRepresentation entity, boolean backendFailover) {
final long timestamp = ThreadLocalRandom.current().nextLong(100000);
var realmFinal = testRealmName;
var clientFinal = clientId;
var idFinal = entity.getId();
getTestingClientFor(getCurrentFailNode()).server().run(session -> {
var realm = session.realms().getRealmByName(realmFinal);
session.getContext().setRealm(realm);
var factory = (AuthorizationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class);
var storeFactory = factory.create(session, realm).getStoreFactory();
var client = session.clients().getClientById(realm, clientFinal);
var resourceServer = storeFactory.getResourceServerStore().findByClient(client);
var ticket = storeFactory.getPermissionTicketStore().findById(resourceServer, idFinal);
ticket.setGrantedTimestamp(timestamp);
});
if (backendFailover) {
failure();
}
for (var node : getCurrentSurvivorNodes()) {
var rsp = getTestingClientFor(node).server().fetchString(session -> {
var realm = session.realms().getRealmByName(realmFinal);
session.getContext().setRealm(realm);
var factory = (AuthorizationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class);
var storeFactory = factory.create(session, realm).getStoreFactory();
var client = session.clients().getClientById(realm, clientFinal);
var resourceServer = storeFactory.getResourceServerStore().findByClient(client);
var ticket = storeFactory.getPermissionTicketStore().findById(resourceServer, idFinal);
return Long.toString(ticket.getGrantedTimestamp());
});
assertEquals(timestamp, Long.parseLong(rsp.replaceAll("\"", "")));
}
failback();
iterateCurrentFailNode();
return entity;
}
}