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`
|
||||
|
||||
= 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
|
||||
|
||||
{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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RealmModel> getRealmsStream(String search) {
|
||||
// Retrieve realms from backend
|
||||
return getRealms(getRealmDelegate().getRealmsStream(search));
|
||||
}
|
||||
|
||||
private Stream<RealmModel> getRealms(Stream<RealmModel> backendRealms) {
|
||||
// Return cache delegates to ensure cache invalidated during write operations
|
||||
return backendRealms.map(RealmModel::getId).map(this::getRealm);
|
||||
|
|
|
@ -152,6 +152,16 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
|||
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) {
|
||||
return closing(query.getResultStream().map(session.realms()::getRealm).filter(Objects::nonNull));
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ import java.util.Set;
|
|||
@Entity
|
||||
@NamedQueries({
|
||||
@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="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) {
|
||||
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 -> search.isEmpty() || realm.getName().toLowerCase().contains(search.trim().toLowerCase()))
|
||||
.skip(first)
|
||||
.limit(max)
|
||||
.map((RealmModel realm) -> new RealmNameRepresentation(realm.getName(), realm.getDisplayName()));
|
||||
|
|
|
@ -64,6 +64,15 @@ public interface RealmProvider extends Provider {
|
|||
*/
|
||||
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.
|
||||
* @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.
|
||||
*/
|
||||
void removeExpiredClientInitialAccess();
|
||||
|
||||
|
||||
default void decreaseRemainingCount(RealmModel realm, ClientInitialAccessModel clientInitialAccess) { // Separate provider method to ensure we decrease remainingCount atomically instead of doing classic update
|
||||
realm.decreaseRemainingCount(clientInitialAccess);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue