Fix: admin GUI not working with 1000s of realms
Search by RealmName is done before loading all realms when filtering Closes #31956 Signed-off-by: Youssef El Houti <youssef.elhouti@gmail.com> Signed-off-by: Alexander Schwartz <aschwart@redhat.com> Co-authored-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
parent
e2d7a94459
commit
e8840df0e0
6 changed files with 33 additions and 3 deletions
|
@ -145,6 +145,11 @@ The CLI command `kc.[sh|bat] import` now has placeholder replacement enabled. Pr
|
||||||
|
|
||||||
If you wish to disable placeholder replacement for the `import` command, add the system property `-Dkeycloak.migration.replace-placeholders=false`
|
If you wish to disable placeholder replacement for the `import` command, add the system property `-Dkeycloak.migration.replace-placeholders=false`
|
||||||
|
|
||||||
|
= New Java API to search realms by name
|
||||||
|
|
||||||
|
The `RealmProvider` Java API now contains a new method `Stream<RealmModel> getRealmsStream(String search)` which allows searching for a realm by name.
|
||||||
|
While there is a default implementation which filters the stream after loading it from the provider, implementations are encouraged to provide this with more efficient implementation.
|
||||||
|
|
||||||
= Keystore and trust store default format change
|
= Keystore and trust store default format change
|
||||||
|
|
||||||
{project_name} now determines the format of the keystore and trust store based on the file extension. If the file extension is `.p12`, `.pkcs12` or `.pfx`, the format is PKCS12. If the file extension is `.jks`, `.keystore` or `.truststore`, the format is JKS. If the file extension is `.pem`, `.crt` or `.key`, the format is PEM.
|
{project_name} now determines the format of the keystore and trust store based on the file extension. If the file extension is `.p12`, `.pkcs12` or `.pfx`, the format is PKCS12. If the file extension is `.jks`, `.keystore` or `.truststore`, the format is JKS. If the file extension is `.pem`, `.crt` or `.key`, the format is PEM.
|
||||||
|
|
|
@ -563,6 +563,12 @@ public class RealmCacheSession implements CacheRealmProvider {
|
||||||
return getRealms(getRealmDelegate().getRealmsStream());
|
return getRealms(getRealmDelegate().getRealmsStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<RealmModel> getRealmsStream(String search) {
|
||||||
|
// Retrieve realms from backend
|
||||||
|
return getRealms(getRealmDelegate().getRealmsStream(search));
|
||||||
|
}
|
||||||
|
|
||||||
private Stream<RealmModel> getRealms(Stream<RealmModel> backendRealms) {
|
private Stream<RealmModel> getRealms(Stream<RealmModel> backendRealms) {
|
||||||
// Return cache delegates to ensure cache invalidated during write operations
|
// Return cache delegates to ensure cache invalidated during write operations
|
||||||
return backendRealms.map(RealmModel::getId).map(this::getRealm);
|
return backendRealms.map(RealmModel::getId).map(this::getRealm);
|
||||||
|
|
|
@ -152,6 +152,16 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||||
return getRealms(query);
|
return getRealms(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<RealmModel> getRealmsStream(String search) {
|
||||||
|
if (search.trim().isEmpty()) {
|
||||||
|
return getRealmsStream();
|
||||||
|
}
|
||||||
|
TypedQuery<String> query = em.createNamedQuery("getRealmIdsWithNameContaining", String.class);
|
||||||
|
query.setParameter("search", search);
|
||||||
|
return getRealms(query);
|
||||||
|
}
|
||||||
|
|
||||||
private Stream<RealmModel> getRealms(TypedQuery<String> query) {
|
private Stream<RealmModel> getRealms(TypedQuery<String> query) {
|
||||||
return closing(query.getResultStream().map(session.realms()::getRealm).filter(Objects::nonNull));
|
return closing(query.getResultStream().map(session.realms()::getRealm).filter(Objects::nonNull));
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ import java.util.Set;
|
||||||
@Entity
|
@Entity
|
||||||
@NamedQueries({
|
@NamedQueries({
|
||||||
@NamedQuery(name="getAllRealmIds", query="select realm.id from RealmEntity realm"),
|
@NamedQuery(name="getAllRealmIds", query="select realm.id from RealmEntity realm"),
|
||||||
|
@NamedQuery(name="getRealmIdsWithNameContaining", query="select realm.id from RealmEntity realm where LOWER(realm.name) like CONCAT('%', LOWER(:search), '%')"),
|
||||||
@NamedQuery(name="getRealmIdByName", query="select realm.id from RealmEntity realm where realm.name = :name"),
|
@NamedQuery(name="getRealmIdByName", query="select realm.id from RealmEntity realm where realm.name = :name"),
|
||||||
@NamedQuery(name="getRealmIdsWithProviderType", query="select distinct c.realm.id from ComponentEntity c where c.providerType = :providerType"),
|
@NamedQuery(name="getRealmIdsWithProviderType", query="select distinct c.realm.id from ComponentEntity c where c.providerType = :providerType"),
|
||||||
})
|
})
|
||||||
|
|
|
@ -55,9 +55,8 @@ public class UIRealmsResource {
|
||||||
@QueryParam("search") @DefaultValue("") String search) {
|
@QueryParam("search") @DefaultValue("") String search) {
|
||||||
final RealmsPermissionEvaluator eval = AdminPermissions.realms(session, auth.adminAuth());
|
final RealmsPermissionEvaluator eval = AdminPermissions.realms(session, auth.adminAuth());
|
||||||
|
|
||||||
return session.realms().getRealmsStream()
|
return session.realms().getRealmsStream(search)
|
||||||
.filter(realm -> eval.canView(realm) || eval.isAdmin(realm))
|
.filter(realm -> eval.canView(realm) || eval.isAdmin(realm))
|
||||||
.filter(realm -> search.isEmpty() || realm.getName().toLowerCase().contains(search.trim().toLowerCase()))
|
|
||||||
.skip(first)
|
.skip(first)
|
||||||
.limit(max)
|
.limit(max)
|
||||||
.map((RealmModel realm) -> new RealmNameRepresentation(realm.getName(), realm.getDisplayName()));
|
.map((RealmModel realm) -> new RealmNameRepresentation(realm.getName(), realm.getDisplayName()));
|
||||||
|
|
|
@ -64,6 +64,15 @@ public interface RealmProvider extends Provider {
|
||||||
*/
|
*/
|
||||||
Stream<RealmModel> getRealmsStream();
|
Stream<RealmModel> getRealmsStream();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns realms as a stream filtered by search.
|
||||||
|
* @param search String to search for in realm names
|
||||||
|
* @return Stream of {@link RealmModel}. Never returns {@code null}.
|
||||||
|
*/
|
||||||
|
default Stream<RealmModel> getRealmsStream(String search) {
|
||||||
|
return getRealmsStream().filter(realm -> search.isEmpty() || realm.getName().toLowerCase().contains(search.trim().toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns stream of realms which has component with the given provider type.
|
* Returns stream of realms which has component with the given provider type.
|
||||||
* @param type {@code Class<?>} Type of the provider.
|
* @param type {@code Class<?>} Type of the provider.
|
||||||
|
@ -101,7 +110,7 @@ public interface RealmProvider extends Provider {
|
||||||
* Removes all expired client initial accesses from all realms.
|
* Removes all expired client initial accesses from all realms.
|
||||||
*/
|
*/
|
||||||
void removeExpiredClientInitialAccess();
|
void removeExpiredClientInitialAccess();
|
||||||
|
|
||||||
default void decreaseRemainingCount(RealmModel realm, ClientInitialAccessModel clientInitialAccess) { // Separate provider method to ensure we decrease remainingCount atomically instead of doing classic update
|
default void decreaseRemainingCount(RealmModel realm, ClientInitialAccessModel clientInitialAccess) { // Separate provider method to ensure we decrease remainingCount atomically instead of doing classic update
|
||||||
realm.decreaseRemainingCount(clientInitialAccess);
|
realm.decreaseRemainingCount(clientInitialAccess);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue