mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-01 12:40:38 -05:00
Reject search for not allowed client attributes
Closes #42541 Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
This commit is contained in:
committed by
Marek Posolda
parent
00f372fa32
commit
0bfb9079f2
@@ -1001,9 +1001,14 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||
|
||||
@Override
|
||||
public Stream<ClientModel> searchClientsByAttributes(RealmModel realm, Map<String, String> attributes, Integer firstResult, Integer maxResults) {
|
||||
Map<String, String> filteredAttributes = clientSearchableAttributes == null ? attributes :
|
||||
attributes.entrySet().stream().filter(m -> clientSearchableAttributes.contains(m.getKey()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
Map<String, String> filteredAttributes = attributes;
|
||||
if (clientSearchableAttributes != null) {
|
||||
Set<String> notAllowed = attributes.keySet().stream().filter(attr -> !clientSearchableAttributes.contains(attr)).collect(Collectors.toSet());
|
||||
if (!notAllowed.isEmpty()) {
|
||||
throw new ModelException("Attributes [" + String.join(", ", notAllowed) + "] not allowed for search");
|
||||
}
|
||||
filteredAttributes = attributes.entrySet().stream().filter(e -> clientSearchableAttributes.contains(e.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<String> queryBuilder = builder.createQuery(String.class);
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.keycloak.events.admin.ResourceType;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.ModelValidationException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
@@ -122,30 +123,34 @@ public class ClientsResource {
|
||||
|
||||
boolean canView = AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm) || auth.clients().canView();
|
||||
Stream<ClientModel> clientModels = Stream.empty();
|
||||
|
||||
if (searchQuery != null) {
|
||||
Map<String, String> attributes = SearchQueryUtils.getFields(searchQuery);
|
||||
clientModels = canView
|
||||
? realm.searchClientByAttributes(attributes, firstResult, maxResults)
|
||||
: realm.searchClientByAttributes(attributes, -1, -1);
|
||||
} else if (clientId == null || clientId.trim().equals("")) {
|
||||
clientModels = canView
|
||||
? realm.getClientsStream(firstResult, maxResults)
|
||||
: realm.getClientsStream();
|
||||
} else if (search) {
|
||||
clientModels = canView
|
||||
? realm.searchClientByClientIdStream(clientId, firstResult, maxResults)
|
||||
: realm.searchClientByClientIdStream(clientId, -1, -1);
|
||||
} else {
|
||||
ClientModel client = realm.getClientByClientId(clientId);
|
||||
if (client != null) {
|
||||
if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
clientModels = Stream.of(client).filter(auth.clients()::canView);
|
||||
} else {
|
||||
clientModels = Stream.of(client);
|
||||
try {
|
||||
if (searchQuery != null) {
|
||||
Map<String, String> attributes = SearchQueryUtils.getFields(searchQuery);
|
||||
clientModels = canView
|
||||
? realm.searchClientByAttributes(attributes, firstResult, maxResults)
|
||||
: realm.searchClientByAttributes(attributes, -1, -1);
|
||||
} else if (clientId == null || clientId.trim().equals("")) {
|
||||
clientModels = canView
|
||||
? realm.getClientsStream(firstResult, maxResults)
|
||||
: realm.getClientsStream();
|
||||
} else if (search) {
|
||||
clientModels = canView
|
||||
? realm.searchClientByClientIdStream(clientId, firstResult, maxResults)
|
||||
: realm.searchClientByClientIdStream(clientId, -1, -1);
|
||||
} else {
|
||||
ClientModel client = realm.getClientByClientId(clientId);
|
||||
if (client != null) {
|
||||
if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
clientModels = Stream.of(client).filter(auth.clients()::canView);
|
||||
} else {
|
||||
clientModels = Stream.of(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ModelException e) {
|
||||
throw new ErrorResponseException(Errors.INVALID_REQUEST, e.getMessage(), Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
Stream<ClientRepresentation> s = ModelToRepresentation.filterValidRepresentations(clientModels,
|
||||
c -> {
|
||||
|
||||
@@ -17,8 +17,13 @@
|
||||
|
||||
package org.keycloak.tests.admin.client;
|
||||
|
||||
import jakarta.ws.rs.ClientErrorException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.tests.utils.matchers.Matchers;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
@@ -28,10 +33,11 @@ public class ClientSearchJpaTest extends AbstractClientSearchTest {
|
||||
|
||||
@Test
|
||||
public void testJpaSearchableAttributesUnset() {
|
||||
// JPA store removes all attributes by default, i.e. returns all clients
|
||||
String[] expectedRes = {CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3, "account", "account-console", "admin-cli", "broker", "realm-management", "security-admin-console"};
|
||||
|
||||
search(String.format("%s:%s", ATTR_ORG_NAME, ATTR_ORG_VAL), expectedRes);
|
||||
try {
|
||||
search(String.format("%s:%s", "wrong_name", "wrong_value"));
|
||||
} catch (ClientErrorException ex) {
|
||||
assertThat(ex.getResponse(), Matchers.statusCodeIs(Response.Status.BAD_REQUEST));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,10 +17,15 @@
|
||||
|
||||
package org.keycloak.tests.admin.client;
|
||||
|
||||
import jakarta.ws.rs.ClientErrorException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.testframework.server.KeycloakServerConfig;
|
||||
import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
|
||||
import org.keycloak.tests.utils.matchers.Matchers;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
@@ -36,9 +41,12 @@ public class ClientSearchTest extends AbstractClientSearchTest {
|
||||
search(String.format("%s:%s %s:%s", ATTR_ORG_NAME, "wrong val", ATTR_URL_NAME, ATTR_URL_VAL));
|
||||
search(String.format("%s:%s", ATTR_QUOTES_NAME_ESCAPED, ATTR_QUOTES_VAL_ESCAPED), CLIENT_ID_3);
|
||||
|
||||
// "filtered" attribute won't take effect when JPA is used
|
||||
String[] expectedRes = new String[]{CLIENT_ID_1, CLIENT_ID_2};
|
||||
search(String.format("%s:%s %s:%s", ATTR_URL_NAME, ATTR_URL_VAL, ATTR_FILTERED_NAME, ATTR_FILTERED_VAL), expectedRes);
|
||||
// reject request because "filtered" attribute is not allowed
|
||||
try {
|
||||
search(String.format("%s:%s %s:%s", ATTR_URL_NAME, ATTR_URL_VAL, ATTR_FILTERED_NAME, ATTR_FILTERED_VAL));
|
||||
} catch (ClientErrorException ex) {
|
||||
assertThat(ex.getResponse(), Matchers.statusCodeIs(Response.Status.BAD_REQUEST));
|
||||
}
|
||||
}
|
||||
|
||||
public static class SearchableServer implements KeycloakServerConfig {
|
||||
|
||||
Reference in New Issue
Block a user