Filter invalid resources and scopes when processing entries from the cache (#43223)

Closes #42907

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2025-10-07 08:06:46 -03:00 committed by GitHub
parent f4e2db715c
commit a8e295d326
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 69 additions and 3 deletions

View File

@ -234,6 +234,9 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
ResourceServer resourceServer = getResourceServer();
for (String resourceId : cached.getResourcesIds(session, modelSupplier)) {
Resource resource = resourceStore.findById(resourceServer, resourceId);
if (resource == null) {
continue;
}
cacheSession.cacheResource(resource);
resources.add(resource);
}
@ -305,6 +308,9 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
ScopeStore scopeStore = cacheSession.getScopeStore();
for (String scopeId : cached.getScopesIds(session, modelSupplier)) {
Scope scope = scopeStore.findById(resourceServer, scopeId);
if (scope == null) {
continue;
}
cacheSession.cacheScope(scope);
scopes.add(scope);
}

View File

@ -825,9 +825,9 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
Set<String> resources = query.getResources();
if (consumer != null) {
resources.stream().map(resourceId -> (R) findById(resourceServer, resourceId)).forEach(consumer);
resources.stream().map(resourceId -> (R) findById(resourceServer, resourceId)).filter(Objects::nonNull).forEach(consumer);
} else {
model = resources.stream().map(resourceId -> (R) findById(resourceServer, resourceId)).collect(Collectors.toList());
model = resources.stream().map(resourceId -> (R) findById(resourceServer, resourceId)).filter(Objects::nonNull).collect(Collectors.toList());
}
}

View File

@ -37,6 +37,9 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -59,13 +62,23 @@ import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.ResourceResource;
import org.keycloak.admin.client.resource.ScopePermissionsResource;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.client.AuthorizationDeniedException;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.TokenIntrospectionResponse;
import org.keycloak.authorization.client.util.HttpResponseException;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.ResourceServerStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.common.util.Base64Url;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.events.EventType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.infinispan.authorization.StoreFactoryCacheSession;
import org.keycloak.models.cache.infinispan.authorization.entities.ResourceQuery;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.HardcodedClaim;
@ -97,7 +110,6 @@ import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.oauth.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.RoleBuilder;
import org.keycloak.testsuite.util.RolesBuilder;
@ -2649,6 +2661,54 @@ public class EntitlementAPITest extends AbstractAuthzTest {
assertEquals("scope1", permissions.get(0).getScopes().iterator().next());
}
@Test
public void testDeleteConcurrency() throws Exception {
ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST);
AuthorizationResource authorization = client.authorization();
CountDownLatch successfulIterations = new CountDownLatch(200);
AtomicBoolean stop = new AtomicBoolean(false);
// Thread that will be creating and deleting a resource
new Thread(() -> {
while (!stop.get()) {
String resourceName = "Test Resource";
List<ResourceRepresentation> test = authorization.resources().findByName(resourceName);
if (test.isEmpty()) {
ResourceRepresentation resource = new ResourceRepresentation();
resource.setName(resourceName);
authorization.resources().create(resource).close();
} else {
authorization.resources().resource(test.get(0).getId()).remove();
}
}
}).start();
AuthzClient authzClient = getAuthzClient(AUTHZ_CLIENT_CONFIG);
for (int i = 0; i < 3; i++) {
// Thread that will be requesting permissions against the resource being created and deleted
new Thread(() -> {
while (!stop.get()) {
try {
authzClient.authorization().authorize();
successfulIterations.countDown();
} catch (Exception ignore) {
// unexpected failures will end execution and the latch will not be decremented
stop.set(true);
return;
}
}
}).start();
}
try {
assertTrue(successfulIterations.await(15, TimeUnit.SECONDS));
} finally {
stop.set(true);
}
}
private void testRptRequestWithResourceName(String configFile) {
Metadata metadata = new Metadata();