diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/GraphQLController.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/GraphQLController.java index b36a52d..2311455 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/GraphQLController.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/GraphQLController.java @@ -12,11 +12,9 @@ import net.brianlevine.keycloak.graphql.queries.ErrorQuery; import net.brianlevine.keycloak.graphql.queries.RealmQuery; import net.brianlevine.keycloak.graphql.queries.UserQuery; - import net.brianlevine.keycloak.graphql.util.OverrideTypeInfoGenerator; import org.keycloak.models.KeycloakSession; -import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -68,9 +66,6 @@ public Map executeQuery(String query, String operationName, Keyc return executionResult.toSpecification(); } - public Map executeQuery(String query, String operationName, KeycloakSession session, Request request, HttpHeaders headers) { - return executeQuery(query, operationName, session, request, headers, Collections.emptyMap()); - } public String printSchema() { diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/annotations/GraphQLOverrideTypeName.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/annotations/GraphQLOverrideTypeName.java index 79e305f..2e58912 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/annotations/GraphQLOverrideTypeName.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/annotations/GraphQLOverrideTypeName.java @@ -7,7 +7,8 @@ /** * For InputTypes and Scalars. Classes annotated with GraphQLOverrideTypeName will not have a suffix ('Input' for - * InputTypes and 'Scalar' for Scalar types) appended. + * InputTypes and 'Scalar' for Scalar types) appended. The name of the class or the name given in the + * @GraphQLInputType/@GraphQLScalar annotations will be used without modification. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/queries/RealmQuery.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/queries/RealmQuery.java index 0f5157e..024bab9 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/queries/RealmQuery.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/queries/RealmQuery.java @@ -1,6 +1,7 @@ package net.brianlevine.keycloak.graphql.queries; import jakarta.ws.rs.ForbiddenException; +import net.brianlevine.keycloak.graphql.types.PagingOptions; import net.brianlevine.keycloak.graphql.util.Page; import net.brianlevine.keycloak.graphql.util.Auth; @@ -25,10 +26,7 @@ public class RealmQuery { @GraphQLQuery(name = "realms", description = "Return a collection of realms that are viewable by the caller.") - public Page getRealms( - @GraphQLRootContext GraphQLContext ctx, - @GraphQLArgument(name="limit", defaultValue = "100") int limit, - @GraphQLArgument(name="start", defaultValue = "0") int start) { + public Page getRealms(PagingOptions options, @GraphQLRootContext GraphQLContext ctx) { KeycloakSession session = ctx.get("keycloak.session"); HttpHeaders headers = ctx.get("headers"); @@ -41,11 +39,15 @@ public Page getRealms( .filter(Objects::nonNull) .toList(); + options = options == null ? new PagingOptions() : options; + List realmTypes = realms.stream() + .skip(options.start) + .limit(options.limit) .map(rep -> rep != null ? new RealmType(session, rep) : null) .toList(); - ret = new Page<>(realms.size(), limit, realmTypes); + ret = new Page<>(realms.size(), options.limit, realmTypes); } catch (ForbiddenException e) { ret = Page.emptyPage(); } diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/rest/GraphQLResourceProvider.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/rest/GraphQLResourceProvider.java index 2683419..a5c08cf 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/rest/GraphQLResourceProvider.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/rest/GraphQLResourceProvider.java @@ -18,6 +18,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collections; import java.util.Map; @@ -52,16 +53,23 @@ public void close() { public Response postGraphQL(Map body, @Context Request request, @Context HttpHeaders headers) throws JsonProcessingException { String query = (String)body.get("query"); String operationName = (String)body.get("operationName"); - //String variables = (String)body.get("variables"); + Object variables = body.get("variables"); - // TODO: Deal with variables. + @SuppressWarnings("unchecked") + Map result = graphql.executeQuery( + query, + operationName, + session, + request, + headers, + variables != null ? (Map) variables : Collections.emptyMap()); - Map result = graphql.executeQuery(query, operationName, session, request, headers); ObjectMapper mapper = new ObjectMapper(); - mapper.enable(SerializationFeature.WRITE_NULL_MAP_VALUES); + + //noinspection deprecation + mapper.enable(SerializationFeature.WRITE_NULL_MAP_VALUES); String s = mapper.writeValueAsString(result); - //return Response.ok(s).header("Access-Control-Allow-Origin", "*").header("Access-Control-Allow-Credentials", "true").build(); return Response.ok(s).build(); } diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientPolicyConditionType.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientPolicyConditionType.java new file mode 100644 index 0000000..a741e90 --- /dev/null +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientPolicyConditionType.java @@ -0,0 +1,44 @@ +package net.brianlevine.keycloak.graphql.types; + +import com.fasterxml.jackson.databind.JsonNode; +import io.leangen.graphql.annotations.types.GraphQLType; +import org.keycloak.representations.idm.ClientPolicyConditionRepresentation; + +import java.util.Objects; + +@GraphQLType +@SuppressWarnings("unused") +public class ClientPolicyConditionType { + private final ClientPolicyConditionRepresentation delegate; + + public ClientPolicyConditionType(ClientPolicyConditionRepresentation clientPolicyConditionRepresentation) { + this.delegate = clientPolicyConditionRepresentation; + } + + public String getConditionProviderId() { + return delegate.getConditionProviderId(); + } + + public void setConditionProviderId(String conditionProviderId) { + delegate.setConditionProviderId(conditionProviderId); + } + + public JsonNode getConfiguration() { + return delegate.getConfiguration(); + } + + public void setConfiguration(JsonNode configuration) { + delegate.setConfiguration(configuration); + } + + @Override + public boolean equals(Object o) { + if (delegate.getClass() != o.getClass()) return false; + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } +} diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientPolicyType.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientPolicyType.java new file mode 100644 index 0000000..252c471 --- /dev/null +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientPolicyType.java @@ -0,0 +1,79 @@ +package net.brianlevine.keycloak.graphql.types; + +import io.leangen.graphql.annotations.GraphQLArgument; +import io.leangen.graphql.annotations.GraphQLQuery; +import io.leangen.graphql.annotations.types.GraphQLType; +import net.brianlevine.keycloak.graphql.util.Page; +import org.keycloak.representations.idm.ClientPolicyConditionRepresentation; +import org.keycloak.representations.idm.ClientPolicyRepresentation; + +import java.util.List; + +@GraphQLType +@SuppressWarnings("unused") +public class ClientPolicyType { + private final ClientPolicyRepresentation delegate; + + public ClientPolicyType(ClientPolicyRepresentation clientPolicyRepresentation) { + this.delegate = clientPolicyRepresentation; + } + + public String getName() { + return delegate.getName(); + } + + public void setName(String name) { + delegate.setName(name); + } + + public String getDescription() { + return delegate.getDescription(); + } + + public void setDescription(String description) { + delegate.setDescription(description); + } + + public Boolean isEnabled() { + return delegate.isEnabled(); + } + + public void setEnabled(Boolean enabled) { + delegate.setEnabled(enabled); + } + + @GraphQLQuery + public Page getConditions(@GraphQLArgument PagingOptions options) { + List reps = delegate.getConditions(); + List conditions = reps.stream() + .skip(options.start) + .limit(options.limit) + .map(ClientPolicyConditionType::new) + .toList(); + + return new Page<>(reps.size(), options.limit, conditions); + } + + public void setConditions(List conditions) { + delegate.setConditions(conditions); + } + + public List getProfiles() { + return delegate.getProfiles(); + } + + public void setProfiles(List profiles) { + delegate.setProfiles(profiles); + } + + @SuppressWarnings("com.intellij.jpb.inspection.EqualsDoesntCheckParameterClassInspection") + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } +} diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientProfileType.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientProfileType.java index bc0a4ff..599cbd3 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientProfileType.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientProfileType.java @@ -1,6 +1,7 @@ package net.brianlevine.keycloak.graphql.types; import io.leangen.graphql.annotations.GraphQLArgument; +import io.leangen.graphql.annotations.GraphQLQuery; import io.leangen.graphql.annotations.types.GraphQLType; import net.brianlevine.keycloak.graphql.util.Page; import org.keycloak.representations.idm.ClientPolicyExecutorRepresentation; @@ -34,15 +35,18 @@ public void setDescription(String description) { delegate.setDescription(description); } - public Page getExecutors(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { + @GraphQLQuery + public Page getExecutors(PagingOptions options) { List executors = delegate.getExecutors(); + + options = options == null ? new PagingOptions() : options; List ets = executors.stream() - .skip(start) - .limit(limit) + .skip(options.start) + .limit(options.limit) .map(ClientPolicyExecutorType::new) .toList(); - return new Page<>(executors.size(), limit, ets); + return new Page<>(executors.size(), options.limit, ets); } public void setExecutors(List executors) { diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientScopeType.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientScopeType.java index ab3a3f0..bf2e28a 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientScopeType.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ClientScopeType.java @@ -42,14 +42,16 @@ public void setDescription(String description) { delegate.setDescription(description); } - public Page getProtocolMappers(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { + public Page getProtocolMappers(PagingOptions options) { + options = options == null ? new PagingOptions() : options; + List pms = delegate.getProtocolMappers().stream() - .skip(start) - .limit(limit) + .skip(options.start) + .limit(options.limit) .map(ProtocolMapperType::new) .toList(); - return new Page<>(pms.size(), limit, pms); + return new Page<>(pms.size(), options.limit, pms); } public void setProtocolMappers(List protocolMappers) { @@ -64,8 +66,8 @@ public void setProtocol(String protocol) { delegate.setProtocol(protocol); } - public AttributeMap getAttributes(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { - return new AttributeMap(delegate.getAttributes(), start, limit); + public AttributeMap getAttributes(PagingOptions options) { + return new AttributeMap(delegate.getAttributes(), options.start, options.limit); } public void setAttributes(Map attributes) { diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ComponentType.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ComponentType.java index 73b43e8..3d86eed 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ComponentType.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ComponentType.java @@ -54,10 +54,11 @@ public void setSubType(String subType) { } @GraphQLQuery - public MultiAttributeMap getConfig(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { + public MultiAttributeMap getConfig(PagingOptions options) { MultivaluedMap config = delegate.getConfig(); + options = options == null ? new PagingOptions() : options; - return new MultiAttributeMap(config, start, limit); + return new MultiAttributeMap(config, options.start, options.limit); } // public void setConfig(MultivaluedHashMap config) { @@ -73,14 +74,14 @@ public MultiAttributeMap getConfig(@GraphQLArgument(defaultValue = "0")int start // } @GraphQLQuery - public ComponentMap getSubComponents(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { + public ComponentMap getSubComponents(PagingOptions options) { MultivaluedHashMap subComponents = delegate.getSubComponents(); Map> subs = subComponents != null ? subComponents.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream().map(ComponentType::new).toList())) : new MultivaluedHashMap<>(); - - return new ComponentMap(subs, start, limit); + options = options == null ? new PagingOptions() : options; + return new ComponentMap(subs, options.start, options.limit); } // public void setSubComponents(MultivaluedHashMap subComponents) { diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/GroupHolder.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/GroupHolder.java index 6874228..5c3d7d6 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/GroupHolder.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/GroupHolder.java @@ -12,19 +12,16 @@ public interface GroupHolder extends BaseType { @GraphQLQuery - default Page getGroups( - @GraphQLArgument(defaultValue = "0") int start, - @GraphQLArgument(defaultValue = "100") int limit, - @GraphQLRootContext GraphQLContext ctx) { - + default Page getGroups(PagingOptions options, @GraphQLRootContext GraphQLContext ctx) { + options = options == null ? new PagingOptions() : options; long totalCount = getGroupsCount(ctx); - Stream groupModels = getGroupsStream(start, limit, ctx); + Stream groupModels = getGroupsStream(options.start, options.limit, ctx); KeycloakSession kcSession = getKeycloakSession(); RealmModel realmModel = getRealmModel(); List groups = groupModels.map(gm -> new GroupType(kcSession, realmModel, gm)).toList(); - return new Page<>((int) totalCount, limit, groups); + return new Page<>((int) totalCount, options.limit, groups); } // Note: Implementations should make these as @GraphQLIgnore diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/GroupType.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/GroupType.java index 4e25984..c6c5648 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/GroupType.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/GroupType.java @@ -147,10 +147,7 @@ public void setAttributes(Map> attributes) { // } @GraphQLQuery - public Page getSubGroups( - @GraphQLArgument(defaultValue = "0")int start, - @GraphQLArgument(defaultValue = "100")int limit, - @GraphQLRootContext GraphQLContext ctx) { + public Page getSubGroups(PagingOptions options, @GraphQLRootContext GraphQLContext ctx) { Page ret; @@ -165,12 +162,14 @@ public Page getSubGroups( if (eval.canView(getGroupModel())) { boolean canViewGlobal = eval.canView(); + options = options == null ? new PagingOptions() : options; + long subGroupCount = gm.getSubGroupsStream().filter(g -> canViewGlobal || eval.canView(g)).count(); - Stream groupModels = gm.getSubGroupsStream(start, limit).filter(g -> canViewGlobal || eval.canView(g)); + Stream groupModels = gm.getSubGroupsStream(options.start, options.limit).filter(g -> canViewGlobal || eval.canView(g)); List groupTypes = groupModels.map(g -> new GroupType(session, realmModel, GroupUtils.toRepresentation(eval, g, true))).toList(); - ret = new Page<>((int)subGroupCount, limit, groupTypes); + ret = new Page<>((int)subGroupCount, options.limit, groupTypes); } else { ret = Page.emptyPage(); diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/OrganizationType.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/OrganizationType.java index 21b3b4c..b0d7efd 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/OrganizationType.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/OrganizationType.java @@ -57,8 +57,10 @@ public void setDescription(String description) { } @GraphQLQuery - public MultiAttributeMap getAttributes(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { - return new MultiAttributeMap(delegate.getAttributes(), start, limit); + public MultiAttributeMap getAttributes(PagingOptions options) { + options = options == null ? new PagingOptions() : options; + + return new MultiAttributeMap(delegate.getAttributes(), options.start, options.limit); } // // public void setAttributes(Map> attributes) { @@ -72,9 +74,11 @@ public MultiAttributeMap getAttributes(@GraphQLArgument(defaultValue = "0")int s // return delegate; // } - public Page getDomains(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { + public Page getDomains(PagingOptions options) { Set domains = delegate.getDomains(); - return new Page<>(domains.size(), limit, domains.stream().skip(start).limit(limit).map(OrganizationDomainType::new).toList()); + options = options == null ? new PagingOptions() : options; + + return new Page<>(domains.size(), options.limit, domains.stream().skip(options.start).limit(options.limit).map(OrganizationDomainType::new).toList()); } public OrganizationDomainType getDomain(String name) { @@ -93,15 +97,18 @@ public OrganizationDomainType getDomain(String name) { // getDomains().remove(domain); // } - public Page getMembers(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { + public Page getMembers(PagingOptions options) { List members = delegate.getMembers(); + + options = options == null ? new PagingOptions() : options; + List users = members.stream() - .skip(start) - .limit(limit) + .skip(options.start) + .limit(options.limit) .map(u -> new UserType(kcSession, kcSession.getContext().getRealm(), u)) .toList(); - return new Page<>(members.size(), limit, users); + return new Page<>(members.size(), options.limit, users); } // public void setMembers(List members) { @@ -115,15 +122,16 @@ public Page getMembers(@GraphQLArgument(defaultValue = "0")int start, // delegate.getMembers().add(user); // } - public Page getIdentityProviders(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { + public Page getIdentityProviders(PagingOptions options) { List identityProviders = delegate.getIdentityProviders(); + options = options == null ? new PagingOptions() : options; List idps = identityProviders.stream() - .skip(start) - .limit(limit) + .skip(options.start) + .limit(options.limit) .map(IdentityProviderType::new) .toList(); - return new Page<>(identityProviders.size(), limit, idps); + return new Page<>(identityProviders.size(), options.limit, idps); } // public void setIdentityProviders(List identityProviders) { diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/StdCollectionArgs.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/PagingOptions.java similarity index 63% rename from graphql/src/main/java/net/brianlevine/keycloak/graphql/types/StdCollectionArgs.java rename to graphql/src/main/java/net/brianlevine/keycloak/graphql/types/PagingOptions.java index b160046..aba372a 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/StdCollectionArgs.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/PagingOptions.java @@ -2,18 +2,22 @@ import io.leangen.graphql.annotations.GraphQLInputField; import io.leangen.graphql.annotations.types.GraphQLType; +import io.leangen.graphql.execution.SortField; import net.brianlevine.keycloak.graphql.annotations.GraphQLOverrideTypeName; - @GraphQLOverrideTypeName -@GraphQLType(name = "PagingConfig", description = "Standard arguments for controlling the paging behavior of collections.") -public class StdCollectionArgs { +@GraphQLType(name = "PagingOptions") +public class PagingOptions { + @GraphQLInputField(defaultValue="0") public int start = 0; @GraphQLInputField(defaultValue="100") public int limit = 100; - @GraphQLInputField(defaultValue="null") - public String[] sort = null; + @GraphQLInputField(defaultValue="[]", description="** Not yet supported **") + public SortField[] sort = null; + + } + diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ProtocolMapperType.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ProtocolMapperType.java index 26f95eb..bd61bfc 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ProtocolMapperType.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/ProtocolMapperType.java @@ -47,9 +47,11 @@ public void setProtocolMapper(String protocolMapper) { delegate.setProtocolMapper(protocolMapper); } - public AttributeMap getConfig(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { + public AttributeMap getConfig(PagingOptions options) { + options = options == null ? new PagingOptions() : options; - return new AttributeMap(delegate.getConfig(), start, limit); + + return new AttributeMap(delegate.getConfig(), options.start, options.limit); } public void setConfig(Map config) { diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/RealmType.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/RealmType.java index 1fda0c0..b787d61 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/RealmType.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/RealmType.java @@ -21,6 +21,7 @@ import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.ClientPoliciesRepresentation; +import org.keycloak.representations.idm.ClientPolicyRepresentation; import org.keycloak.representations.idm.ClientProfileRepresentation; import org.keycloak.representations.idm.ClientProfilesRepresentation; import org.keycloak.representations.idm.ComponentExportRepresentation; @@ -44,7 +45,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; - @GraphQLType @SuppressWarnings("unused") public class RealmType implements Container, GroupHolder, RoleHolder, BaseType { @@ -162,17 +162,18 @@ public void setDisplayNameHtml(String displayNameHtml) { } @GraphQLQuery - //public Page getUsers(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit, @GraphQLRootContext GraphQLContext ctx) { - public Page getUsers(@GraphQLArgument(name="args") StdCollectionArgs collectionArgs, @GraphQLRootContext GraphQLContext ctx) { + public Page getUsers(PagingOptions options, @GraphQLRootContext GraphQLContext ctx) { RealmModel realm = kcSession.realms().getRealm(getId()); UserPermissionEvaluator eval = Auth.getAdminPermissionEvaluator(ctx, getRealmModel()).users(); - Stream userModels = kcSession.users().searchForUserStream(realm, Collections.emptyMap(), collectionArgs.start, collectionArgs.limit); + options = options == null ? new PagingOptions() : options; + + Stream userModels = kcSession.users().searchForUserStream(realm, Collections.emptyMap(), options.start, options.limit); Stream userReps = RealmType.toUserRepresentation(kcSession, realm, eval, userModels); List userTypes = userReps.map(u -> new UserType(kcSession, realm, u)).toList(); int userCount = userTypes.size(); - Page page = new Page<>(userCount, collectionArgs.limit, userTypes); + Page page = new Page<>(userCount, options.limit, userTypes); return page; } @@ -287,12 +288,12 @@ public static Stream toUserRepresentation(KeycloakSession se } @GraphQLQuery - public Page getClients(@GraphQLArgument(defaultValue = "0") int start, @GraphQLArgument(defaultValue = "100") int limit) { - Stream clientModels = getRealmModel().getClientsStream(start, limit); + public Page getClients(@GraphQLArgument PagingOptions options) { + Stream clientModels = getRealmModel().getClientsStream(options.start, options.limit); List clients = clientModels.map(c -> new ClientType(getKeycloakSession(), getRealmModel(), c)).toList(); long count = getRealmModel().getClientsCount(); - return new Page<>((int)count, limit, clients); + return new Page<>((int)count, options.limit, clients); } @@ -562,7 +563,7 @@ public RoleType getDefaultRole() { // TODO: Optimize with SQL query rather than iterating over groups by name @GraphQLQuery - public Page getDefaultGroups(@GraphQLArgument(defaultValue = "0") int start, @GraphQLArgument(defaultValue = "100") int limit, @GraphQLRootContext GraphQLContext ctx) { + public Page getDefaultGroups(@GraphQLArgument PagingOptions options, @GraphQLRootContext GraphQLContext ctx) { AdminPermissionEvaluator auth = Auth.getAdminPermissionEvaluator(ctx.get("headers"), getKeycloakSession(), getRealmModel()); if (auth.realm().canViewRealm()) { @@ -572,10 +573,10 @@ public Page getDefaultGroups(@GraphQLArgument(defaultValue = "0") int Stream groups = realm.getDefaultGroupsStream(); long count = groups.count(); - groups = realm.getDefaultGroupsStream().skip(start).limit(limit); + groups = realm.getDefaultGroupsStream().skip(options.start).limit(options.limit); List groupTypes = groups.map(g -> new GroupType(session, realm, g)).toList(); - return new Page<>((int)count, limit, groupTypes); + return new Page<>((int)count, options.limit, groupTypes); } else { return Page.emptyPage(); @@ -1343,38 +1344,64 @@ public void setWebAuthnPolicyPasswordlessExtraOrigins(List extraOrigins) @GraphQLQuery - public Page getClientProfiles(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100") int limit) { + public Page getClientProfiles(@GraphQLArgument PagingOptions options) { Page ret = Page.emptyPage(); ClientProfilesRepresentation rep = delegate.getParsedClientProfiles(); if (rep != null) { List profiles = rep.getProfiles(); - List cpt = profiles.stream().skip(start).limit(limit).map(ClientProfileType::new).toList(); - ret = new Page<>(profiles.size(), limit, cpt); + List cpt = profiles.stream().skip(options.start).limit(options.limit).map(ClientProfileType::new).toList(); + ret = new Page<>(profiles.size(), options.limit, cpt); } return ret; } @GraphQLQuery - public Page getGlobalClientProfiles(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100") int limit) { + public Page getGlobalClientProfiles(@GraphQLArgument PagingOptions options) { Page ret = Page.emptyPage(); - ClientProfilesRepresentation rep = delegate.getParsedClientProfiles(); if (rep != null) { List profiles = rep.getGlobalProfiles(); - List cpt = profiles.stream().skip(start).limit(limit).map(ClientProfileType::new).toList(); - ret = new Page<>(profiles.size(), limit, cpt); + List cpt = profiles.stream().skip(options.start).limit(options.limit).map(ClientProfileType::new).toList(); + ret = new Page<>(profiles.size(), options.limit, cpt); } return ret; } - public ClientPoliciesRepresentation getParsedClientPolicies() { - return delegate.getParsedClientPolicies(); + @GraphQLQuery + public Page getClientPolicies(@GraphQLArgument PagingOptions options) { + Page ret = Page.emptyPage(); + ClientPoliciesRepresentation rep = delegate.getParsedClientPolicies(); + + if (rep != null) { + List policies = rep.getPolicies(); + List cpt = policies.stream().skip(options.start).limit(options.limit).map(ClientPolicyType::new).toList(); + ret = new Page<>(policies.size(), options.limit, cpt); + } + + return ret; } + + public Page getGlobalClientPolicies(@GraphQLArgument PagingOptions options) { + Page ret = Page.emptyPage(); + ClientPoliciesRepresentation rep = delegate.getParsedClientPolicies(); + + if (rep != null) { + List policies = rep.getGlobalPolicies(); + List cpt = policies.stream().skip(options.start).limit(options.limit).map(ClientPolicyType::new).toList(); + ret = new Page<>(policies.size(), options.limit, cpt); + } + + return ret; + } + +// public ClientPoliciesRepresentation getParsedClientPolicies() { +// return delegate.getParsedClientPolicies(); +// } // // // public void setParsedClientPolicies(ClientPoliciesRepresentation clientPolicies) { @@ -1461,9 +1488,9 @@ public void setKeycloakVersion(String keycloakVersion) { delegate.setKeycloakVersion(keycloakVersion); } - public Page getClientScopes(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { - List clientScopes = delegate.getClientScopes().stream().skip(start).limit(limit).map(ClientScopeType::new).toList(); - return new Page<>(clientScopes.size(), limit, clientScopes); + public Page getClientScopes(@GraphQLArgument PagingOptions options) { + List clientScopes = delegate.getClientScopes().stream().skip(options.start).limit(options.limit).map(ClientScopeType::new).toList(); + return new Page<>(clientScopes.size(), options.limit, clientScopes); } // public void setClientScopes(List clientScopes) { @@ -1496,14 +1523,14 @@ public Page getClientScopes(@GraphQLArgument(defaultValue = "0" // } @GraphQLQuery - public ComponentMap getComponents(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { + public ComponentMap getComponents(@GraphQLArgument PagingOptions options) { RealmModel realm = getRealmModel(); MultivaluedHashMap components = ExportUtils.exportComponents(realm, realm.getId()); Map> comps = components.entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream().map(ComponentType::new).toList())); - return new ComponentMap(comps, start, limit); + return new ComponentMap(comps, options.start, options.limit); } // @@ -1523,22 +1550,22 @@ public void setAttributes(Map attributes) { @GraphQLQuery - public AttributeMap getAttributes(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { - return new AttributeMap(delegate.getAttributes(), start, limit); + public AttributeMap getAttributes(@GraphQLArgument PagingOptions options) { + return new AttributeMap(delegate.getAttributes(), options.start, options.limit); } @GraphQLQuery - public Page getFederatedUsers(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { + public Page getFederatedUsers(@GraphQLArgument PagingOptions options) { List federatedUsers = delegate.getFederatedUsers(); List users = federatedUsers.stream() - .skip(start) - .limit(limit) + .skip(options.start) + .limit(options.limit) .map(f -> new UserType(getKeycloakSession(), getRealmModel(), f)) .toList(); - return new Page<>(federatedUsers.size(), limit, users); + return new Page<>(federatedUsers.size(), options.limit, users); } // // @@ -1571,12 +1598,11 @@ public Map getAttributesOrEmpty() { return delegate.getAttributesOrEmpty(); } - - public Page getOrganizations(@GraphQLArgument(defaultValue = "0")int start, @GraphQLArgument(defaultValue = "100")int limit) { + public Page getOrganizations(@GraphQLArgument PagingOptions options) { List organizations = delegate.getOrganizations(); - List organizationTypes = organizations.stream().skip(start).limit(100).map(o -> new OrganizationType(getKeycloakSession(), o)).toList(); + List organizationTypes = organizations.stream().skip(options.start).limit(options.limit).map(o -> new OrganizationType(getKeycloakSession(), o)).toList(); - return new Page<>(organizations.size(), limit, organizationTypes); + return new Page<>(organizations.size(), options.limit, organizationTypes); } // // diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/RoleHolder.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/RoleHolder.java index 9fd3207..b36a263 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/RoleHolder.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/RoleHolder.java @@ -15,13 +15,10 @@ public interface RoleHolder extends BaseType { @GraphQLQuery - default Page getRoles( - @GraphQLArgument(defaultValue = "0") int start, - @GraphQLArgument(defaultValue = "100") int limit, - @GraphQLRootContext GraphQLContext ctx) { + default Page getRoles(PagingOptions options, @GraphQLRootContext GraphQLContext ctx) { long count = getRolesCount(ctx); - Stream roles = getRolesStream(start, limit, ctx); + Stream roles = getRolesStream(options.start, options.limit, ctx); List roleTypes = roles.map(r -> { @@ -29,7 +26,7 @@ default Page getRoles( return new RoleType(getKeycloakSession(), getRealmModel(), roleRep); }).toList(); - return new Page<>((int) count, limit, roleTypes); + return new Page<>((int) count, options.limit, roleTypes); } // Note: Implementations should make these as @GraphQLIgnore diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/RoleType.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/RoleType.java index f697224..9e1f81f 100644 --- a/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/RoleType.java +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/types/RoleType.java @@ -92,7 +92,7 @@ public void setDescription(String description) { } @GraphQLQuery(name = "compositeRoles") - public Page getComposites(@GraphQLArgument(defaultValue = "0") int start, @GraphQLArgument(defaultValue = "100") int limit) { + public Page getComposites(PagingOptions options) { JpaConnectionProvider connection = kcSession.getProvider(JpaConnectionProvider.class); EntityManager em = connection.getEntityManager(); @@ -105,11 +105,12 @@ public Page getComposites(@GraphQLArgument(defaultValue = "0") int sta // // long count = (long)q.getSingleResult(); + options = options == null ? new PagingOptions() : options; long count = getRoleModel().getCompositesStream().count(); - Stream composites = getRoleModel().getCompositesStream(null, start, limit); + Stream composites = getRoleModel().getCompositesStream(null, options.start, options.limit); List items = composites.map(c -> new RoleType(kcSession, realm, c)).toList(); - return new Page<>((int)count, limit, items); + return new Page<>((int)count, options.limit, items); } diff --git a/graphql/src/main/java/net/brianlevine/keycloak/graphql/util/MyAnnotedArgumentBuilder.java b/graphql/src/main/java/net/brianlevine/keycloak/graphql/util/MyAnnotedArgumentBuilder.java new file mode 100644 index 0000000..7bf850f --- /dev/null +++ b/graphql/src/main/java/net/brianlevine/keycloak/graphql/util/MyAnnotedArgumentBuilder.java @@ -0,0 +1,15 @@ +package net.brianlevine.keycloak.graphql.util; + +import io.leangen.graphql.execution.GlobalEnvironment; +import io.leangen.graphql.metadata.DefaultValue; +import io.leangen.graphql.metadata.strategy.query.AnnotatedArgumentBuilder; + +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Parameter; + +public class MyAnnotedArgumentBuilder extends AnnotatedArgumentBuilder { + @Override + protected DefaultValue defaultValue(Parameter parameter, AnnotatedType parameterType, GlobalEnvironment environment) { + return super.defaultValue(parameter, parameterType, environment); + } +}