Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client type service account default type #29037

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -19,7 +19,9 @@
package org.keycloak.client.clienttype;

import org.keycloak.models.ClientModel;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientTypeRepresentation;

import java.util.Map;

/**
* TODO:client-types javadocs
Expand All @@ -41,10 +43,8 @@ public interface ClientType {
<T> T getDefaultValue(String optionName, Class<T> optionType);


// Augment at the client type
// Augment particular client on creation of client (TODO:client-types Should it be clientModel or clientRepresentation? Or something else?)
void onCreate(ClientRepresentation newClient) throws ClientTypeException;
Map<String, ClientTypeRepresentation.PropertyConfig> getConfiguration();

// Augment particular client on update of client (TODO:client-types Should it be clientModel or clientRepresentation? Or something else?)
void onUpdate(ClientModel currentClient, ClientRepresentation clientToUpdate) throws ClientTypeException;
// Augment at the client type
ClientModel augment(ClientModel client);
}
Expand Up @@ -23,7 +23,7 @@
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import java.util.List;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicMarkableReference;
Expand Down

Large diffs are not rendered by default.

Expand Up @@ -18,14 +18,15 @@

package org.keycloak.services.clienttype.client;

import java.util.function.Consumer;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import org.keycloak.common.util.ObjectUtil;
import org.keycloak.models.ClientModel;
import org.keycloak.models.delegate.ClientModelLazyDelegate;
import org.keycloak.client.clienttype.ClientType;
import org.keycloak.client.clienttype.ClientTypeException;

/**
* Delegates to client-type and underlying delegate
Expand All @@ -47,45 +48,219 @@ public TypeAwareClientModelDelegate(ClientType clientType, Supplier<ClientModel>

@Override
public boolean isStandardFlowEnabled() {
return getBooleanProperty("standardFlowEnabled", super::isStandardFlowEnabled);
return TypedClientSimpleAttribute.STANDARD_FLOW_ENABLED
.getClientAttribute(clientType, super::isStandardFlowEnabled, Boolean.class);
}

@Override
public void setStandardFlowEnabled(boolean standardFlowEnabled) {
setBooleanProperty("standardFlowEnabled", standardFlowEnabled, super::setStandardFlowEnabled);
TypedClientSimpleAttribute.STANDARD_FLOW_ENABLED
.setClientAttribute(clientType, standardFlowEnabled, super::setStandardFlowEnabled, Boolean.class);
}

@Override
public boolean isBearerOnly() {
return TypedClientSimpleAttribute.BEARER_ONLY
.getClientAttribute(clientType, super::isBearerOnly, Boolean.class);
}

protected boolean getBooleanProperty(String propertyName, Supplier<Boolean> clientGetter) {
// Check if clientType supports the feature. If not, simply return false
if (!clientType.isApplicable(propertyName)) {
return false;
}
@Override
public void setBearerOnly(boolean bearerOnly) {
TypedClientSimpleAttribute.BEARER_ONLY
.setClientAttribute(clientType, bearerOnly, super::setBearerOnly, Boolean.class);
}

@Override
public boolean isConsentRequired() {
return TypedClientSimpleAttribute.CONSENT_REQUIRED
.getClientAttribute(clientType, super::isConsentRequired, Boolean.class);
}

@Override
public void setConsentRequired(boolean consentRequired) {
TypedClientSimpleAttribute.CONSENT_REQUIRED
.setClientAttribute(clientType, consentRequired, super::setConsentRequired, Boolean.class);
}

@Override
public boolean isDirectAccessGrantsEnabled() {
return TypedClientSimpleAttribute.DIRECT_ACCESS_GRANTS_ENABLED
.getClientAttribute(clientType, super::isDirectAccessGrantsEnabled, Boolean.class);
}

@Override
public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
TypedClientSimpleAttribute.DIRECT_ACCESS_GRANTS_ENABLED
.setClientAttribute(clientType, directAccessGrantsEnabled, super::setDirectAccessGrantsEnabled, Boolean.class);
}

@Override
public boolean isAlwaysDisplayInConsole() {
return TypedClientSimpleAttribute.ALWAYS_DISPLAY_IN_CONSOLE
.getClientAttribute(clientType, super::isAlwaysDisplayInConsole, Boolean.class);
}

@Override
public void setAlwaysDisplayInConsole(boolean alwaysDisplayInConsole) {
TypedClientSimpleAttribute.ALWAYS_DISPLAY_IN_CONSOLE
.setClientAttribute(clientType, alwaysDisplayInConsole, super::setAlwaysDisplayInConsole, Boolean.class);
}

@Override
public boolean isFrontchannelLogout() {
return TypedClientSimpleAttribute.FRONTCHANNEL_LOGOUT
.getClientAttribute(clientType, super::isFrontchannelLogout, Boolean.class);
}

@Override
public void setFrontchannelLogout(boolean frontchannelLogout) {
TypedClientSimpleAttribute.FRONTCHANNEL_LOGOUT
.setClientAttribute(clientType, frontchannelLogout, super::setFrontchannelLogout, Boolean.class);
}

@Override
public boolean isImplicitFlowEnabled() {
return TypedClientSimpleAttribute.IMPLICIT_FLOW_ENABLED
.getClientAttribute(clientType, super::isImplicitFlowEnabled, Boolean.class);
}

@Override
public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
TypedClientSimpleAttribute.IMPLICIT_FLOW_ENABLED
.setClientAttribute(clientType, implicitFlowEnabled, super::setImplicitFlowEnabled, Boolean.class);
}

@Override
public boolean isServiceAccountsEnabled() {
return TypedClientSimpleAttribute.SERVICE_ACCOUNTS_ENABLED
.getClientAttribute(clientType, super::isServiceAccountsEnabled, Boolean.class);
}

@Override
public void setServiceAccountsEnabled(boolean flag) {
TypedClientSimpleAttribute.SERVICE_ACCOUNTS_ENABLED
.setClientAttribute(clientType, flag, super::setServiceAccountsEnabled, Boolean.class);
}

@Override
public String getProtocol() {
return TypedClientSimpleAttribute.PROTOCOL
.getClientAttribute(clientType, super::getProtocol, String.class);
}

@Override
public void setProtocol(String protocol) {
TypedClientSimpleAttribute.PROTOCOL
.setClientAttribute(clientType, protocol, super::setProtocol, String.class);
}

@Override
public boolean isPublicClient() {
return TypedClientSimpleAttribute.PUBLIC_CLIENT
.getClientAttribute(clientType, super::isPublicClient, Boolean.class);
}

@Override
public void setPublicClient(boolean flag) {
TypedClientSimpleAttribute.PUBLIC_CLIENT
.setClientAttribute(clientType, flag, super::setPublicClient, Boolean.class);
}

@Override
public Set<String> getWebOrigins() {
return TypedClientSimpleAttribute.WEB_ORIGINS
.getClientAttribute(clientType, super::getWebOrigins, Set.class);
}

@Override
public void setWebOrigins(Set<String> webOrigins) {
TypedClientSimpleAttribute.WEB_ORIGINS
.setClientAttribute(clientType, webOrigins, super::setWebOrigins, Set.class);
}

@Override
public void addWebOrigin(String webOrigin) {
TypedClientSimpleAttribute.WEB_ORIGINS
.setClientAttribute(clientType, webOrigin, super::addWebOrigin, String.class);
}

@Override
public void removeWebOrigin(String webOrigin) {
TypedClientSimpleAttribute.WEB_ORIGINS
.setClientAttribute(clientType, null, (val) -> super.removeWebOrigin(webOrigin), String.class);
}

// Check if this is read-only. If yes, then we just directly delegate to return stuff from the clientType rather than from client
if (clientType.isReadOnly(propertyName)) {
return clientType.getDefaultValue(propertyName, Boolean.class);
@Override
public Set<String> getRedirectUris() {
return TypedClientSimpleAttribute.REDIRECT_URIS
.getClientAttribute(clientType, super::getRedirectUris, Set.class);
}

@Override
public void setRedirectUris(Set<String> redirectUris) {
TypedClientSimpleAttribute.REDIRECT_URIS
.setClientAttribute(clientType, redirectUris, super::setRedirectUris, Set.class);
}

@Override
public void addRedirectUri(String redirectUri) {
TypedClientSimpleAttribute.REDIRECT_URIS
.setClientAttribute(clientType, redirectUri, super::addRedirectUri, String.class);
}

@Override
public void removeRedirectUri(String redirectUri) {
TypedClientSimpleAttribute.REDIRECT_URIS
.setClientAttribute(clientType, null, (val) -> super.removeRedirectUri(redirectUri), String.class);
}

@Override
public void setAttribute(String name, String value) {
TypedClientExtendedAttribute attribute = TypedClientExtendedAttribute.getAttributesByName().get(name);
if (attribute != null) {
attribute.setClientAttribute(clientType, value, (newValue) -> super.setAttribute(name, newValue), String.class);
} else {
super.setAttribute(name, value);
}
}

// Delegate to clientGetter
return clientGetter.get();
@Override
public void removeAttribute(String name) {
TypedClientExtendedAttribute attribute = TypedClientExtendedAttribute.getAttributesByName().get(name);
if (attribute != null) {
attribute.setClientAttribute(clientType, null, (val) -> super.removeAttribute(name), String.class);
} else {
super.removeAttribute(name);
}
}

protected void setBooleanProperty(String propertyName, Boolean newValue, Consumer<Boolean> clientSetter) {
// Check if clientType supports the feature. If not, return directly
if (!clientType.isApplicable(propertyName)) {
return;
@Override
public String getAttribute(String name) {
TypedClientExtendedAttribute attribute = TypedClientExtendedAttribute.getAttributesByName().get(name);
if (attribute != null) {
return attribute.getClientAttribute(clientType, () -> super.getAttribute(name), String.class);
} else {
return super.getAttribute(name);
}
}

@Override
public Map<String, String> getAttributes() {
// Start with attributes set on the delegate.
Map<String, String> attributes = new HashMap<>(super.getAttributes());

// Get extended client type attributes and values from the client type configuration.
Set<String> extendedClientTypeAttributes =
clientType.getConfiguration().entrySet().stream()
.map(Map.Entry::getKey)
.filter(entry -> TypedClientExtendedAttribute.getAttributesByName().containsKey(entry))
.collect(Collectors.toSet());

// Check if this is read-only. If yes and there is an attempt to change some stuff, then throw an exception
if (clientType.isReadOnly(propertyName)) {
Boolean oldVal = clientType.getDefaultValue(propertyName, Boolean.class);
if (!ObjectUtil.isEqualOrBothNull(oldVal, newValue)) {
throw new ClientTypeException("Property " + propertyName + " of client " + getClientId() + " is read-only due to client type " + clientType.getName());
}
// Augment client type attributes on top of attributes on the delegate.
for (String entry : extendedClientTypeAttributes) {
attributes.put(entry, getAttribute(entry));
}

// Call clientSetter
clientSetter.accept(newValue);
return attributes;
}
}