8000 refinements related to service methods, serdes by shawkins · Pull Request #40110 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;

import java.util.LinkedHashSet;
import java.util.Set;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonInclude(JsonInclude.Include.NON_ABSENT)
public class ClientRepresentation {

public static final String OIDC = "openid-connect";

@JsonProperty(required = true)
@JsonPropertyDescription("ID uniquely identifying this client")
private String clientId;
Expand All @@ -19,30 +22,36 @@ public class ClientRepresentation {
@JsonPropertyDescription("Human readable description of the client")
private String description;

@JsonProperty(defaultValue = OIDC)
@JsonPropertyDescription("The protocol used to communicate with the client")
private String protocol;
private String protocol = OIDC;

@JsonPropertyDescription("Whether this client is enabled")
private Boolean enabled;

@JsonPropertyDescription("URL to the application's homepage that is represented by this client")
private String appUrl;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonPropertyDescription("URLs that the browser can redirect to after login")
private Set<String> appRedirectUrls;
private Set<String> appRedirectUrls = new LinkedHashSet<String>();

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonPropertyDescription("Login flows that are enabled for this client")
private Set<String> loginFlows;
private Set<String> loginFlows = new LinkedHashSet<String>();

@JsonPropertyDescription("Authentication configuration for this client")
private Auth auth;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonPropertyDescription("Web origins that are allowed to make requests to this client")
private Set<String> webOrigins;
private Set<String> webOrigins = new LinkedHashSet<String>();

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonPropertyDescription("Roles associated with this client")
private Set<String> roles;
private Set<String> roles = new LinkedHashSet<String>();

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonPropertyDescription("Service account configuration for this client")
private ServiceAccount serviceAccount;

Expand Down Expand Up @@ -148,7 +157,7 @@ public void setServiceAccount(ServiceAccount serviceAccount) {
this.serviceAccount = serviceAccount;
}

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonInclude(JsonInclude.Include.NON_ABSENT)
public static class Auth {

@JsonPropertyDescription("Whether authentication is enabled for this client")
Expand Down Expand Up @@ -196,14 +205,15 @@ public void setCertificate(String certificate) {
}
}

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonInclude(JsonInclude.Include.NON_ABSENT)
public static class ServiceAccount {

@JsonPropertyDescription("Whether the service account is enabled")
private Boolean enabled;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonPropertyDescription("Roles assigned to the service account")
private Set<String> roles;
private Set<String> roles = new LinkedHashSet<String>();

public Boolean getEnabled() {
return enabled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ public interface ClientApi {

@GET
@Produces(MediaType.APPLICATION_JSON)
ClientRepresentation getClient(@QueryParam("runtime") Boolean isRuntime);
ClientRepresentation getClient();
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public DefaultClientApi(KeycloakSession session, String clientId) {
@GET
@Produces(MediaType.APPLICATION_JSON)
@Override
public ClientRepresentation getClient(@QueryParam("runtime") Boolean isRuntime) {
return clientService.getClient(realm, clientId, isRuntime)
public ClientRepresentation getClient() {
return clientService.getClient(realm, clientId, null)
.orElseThrow(() -> new NotFoundException("Cannot find the specified client"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ public DefaultClientsApi(KeycloakSession session, RealmModel realm) {
@Produces(MediaType.APPLICATION_JSON)
@Override
public Stream<ClientRepresentation> getClients() {
return clientService.getClients(realm);
return clientService.getClients(realm, null, null, null);
}

@Override
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Expand All @@ -52,6 +53,7 @@ public ClientRepresentation createOrUpdateClient(ClientRepresentation client) {
}
}

@Override
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,51 @@
import org.keycloak.services.Service;
import org.keycloak.services.ServiceException;

import jakarta.ws.rs.core.Response.Status;

import java.util.Optional;
import java.util.stream.Stream;

public interface ClientService extends Service {

Optional<ClientRepresentation> getClient(RealmModel realm, String clientId);
public static class ClientSearchOptions {
// TODO
}

public static class ClientProjectionOptions {
// TODO
}

public static class ClientSortAndSliceOptions {
// order by
// offset
// limit
// NOTE: this is not always the most desirable way to do pagination
}

Optional<ClientRepresentation> getClient(RealmModel realm, String clientId, ClientProjectionOptions projectionOptions);

Optional<ClientRepresentation> getClient(RealmModel realm, String clientId, Boolean fullRepresentation);
Stream<ClientRepresentation> getClients(RealmModel realm, ClientProjectionOptions projectionOptions, ClientSearchOptions searchOptions, ClientSortAndSliceOptions sortAndSliceOptions);

Stream<ClientRepresentation> getClients(RealmModel realm);
ClientRepresentation deleteClient(RealmModel realm, String clientId);

ClientRepresentation createOrUpdateClient(RealmModel realm, ClientRepresentation client) throws ServiceException;
Stream<ClientRepresentation> deleteClients(RealmModel realm, ClientSearchOptions searchOptions);

default ClientRepresentation createOrUpdateClient(RealmModel realm, ClientRepresentation client) throws ServiceException {
// If we can paramertize the services, it makes sense to define default handling like this
// but this duplicate the existence check
try {
return createClient(realm, client);
} catch (ServiceException e) {
if (!e.getSuggestedResponseStatus().filter(Status.CONFLICT::equals).isPresent()) {
throw e;
}
}
return updateClient(realm, client);
}

ClientRepresentation updateClient(RealmModel realm, ClientRepresentation client) throws ServiceException;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we need some switch/flag for partial/PATCH updates?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we need some switch/flag for partial/PATCH updates?

That will be a separate method.


ClientRepresentation createClient(RealmModel realm, ClientRepresentation client) throws ServiceException;

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.keycloak.services.client;

import jakarta.ws.rs.core.Response;

import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.mapper.ClientModelMapper;
Expand All @@ -22,33 +24,48 @@ public DefaultClientService(KeycloakSession session) {
}

@Override
public Optional<ClientRepresentation> getClient(RealmModel realm, String clientId) {
public Optional<ClientRepresentation> getClient(RealmModel realm, String clientId,
ClientProjectionOptions projectionOptions) {
return Optional.ofNullable(realm.getClientByClientId(clientId)).map(mapper::fromModel);
}

@Override
public Optional<ClientRepresentation> getClient(RealmModel realm, String clientId, Boolean fullRepresentation) {
// TODO reduced client rep
return fullRepresentation != null && fullRepresentation ? getClient(realm, clientId) : Optional.of(getTestReducedClientRep(clientId));
public Stream<ClientRepresentation> getClients(RealmModel realm, ClientProjectionOptions projectionOptions,
ClientSearchOptions searchOptions, ClientSortAndSliceOptions sortAndSliceOptions) {
return realm.getClientsStream().map(mapper::fromModel);
}

@Override
public Stream<ClientRepresentation> getClients(RealmModel realm) {
return realm.getClientsStream().map(mapper::fromModel);
public ClientRepresentation createClient(RealmModel realm, ClientRepresentation client) throws ServiceException {
if (realm.getClientByClientId(client.getClientId()) != null) {
throw new ServiceException("Client already exists", Response.Status.CONFLICT);
}

var model = realm.addClient(client.getClientId());
// TODO: overlay model
return mapper.fromModel(model);
}

@Override
public ClientRepresentation createOrUpdateClient(RealmModel realm, ClientRepresentation client) throws ServiceException {
return null; // TODO
public ClientRepresentation deleteClient(RealmModel realm, String clientId) {
// TODO Auto-generated method stub
return null;
}

@Override
public ClientRepresentation createClient(RealmModel realm, ClientRepresentation client) throws ServiceException {
if (realm.getClientByClientId(client.getClientId()) != null) {
throw new ServiceException("Client already exists", Response.Status.CONFLICT);
public Stream<ClientRepresentation> deleteClients(RealmModel realm, ClientSearchOptions searchOptions) {
// TODO Auto-generated method stub
return null;
}

@Override
public ClientRepresentation updateClient(RealmModel realm, ClientRepresentation client) throws ServiceException {
ClientModel model = realm.getClientByClientId(client.getClientId());
if (model == null) {
throw new ServiceException("Client does not exist", Response.Status.NOT_FOUND);
}

var model = realm.addClient(client.getClientId());
// TODO: overlay model
return mapper.fromModel(model);
}

Expand All @@ -57,8 +74,4 @@ public void close() {

}

// TODO tested reduced client representation
private static ClientRepresentation getTestReducedClientRep(String clientId) {
return new ClientRepresentation(clientId);
}
}
0