mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
Make PermissionTicket events marshallable
Fixes #35328 Signed-off-by: Pedro Ruivo <pruivo@redhat.com>
This commit is contained in:
parent
fda6dde44f
commit
a65fd34bbf
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user