KEYCLOAK-14553 Client map store
Co-Authored-By: vramik <vramik@redhat.com>
This commit is contained in:
parent
2c29c58af1
commit
ac0011ab6f
25 changed files with 2145 additions and 7 deletions
|
@ -0,0 +1,508 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.client;
|
||||||
|
|
||||||
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public abstract class AbstractClientEntity<K> implements AbstractEntity<K> {
|
||||||
|
|
||||||
|
private K id;
|
||||||
|
private String realmId;
|
||||||
|
|
||||||
|
private String clientId;
|
||||||
|
private String name;
|
||||||
|
private String description;
|
||||||
|
private Set<String> redirectUris = new HashSet<>();
|
||||||
|
private boolean enabled;
|
||||||
|
private boolean alwaysDisplayInConsole;
|
||||||
|
private String clientAuthenticatorType;
|
||||||
|
private String secret;
|
||||||
|
private String registrationToken;
|
||||||
|
private String protocol;
|
||||||
|
private Map<String, String> attributes = new HashMap<>();
|
||||||
|
private Map<String, String> authFlowBindings = new HashMap<>();
|
||||||
|
private boolean publicClient;
|
||||||
|
private boolean fullScopeAllowed;
|
||||||
|
private boolean frontchannelLogout;
|
||||||
|
private int notBefore;
|
||||||
|
private Set<String> scope = new HashSet<>();
|
||||||
|
private Set<String> webOrigins = new HashSet<>();
|
||||||
|
private Map<String, ProtocolMapperModel> protocolMappers = new HashMap<>();
|
||||||
|
private Map<String, Boolean> clientScopes = new HashMap<>();
|
||||||
|
private Set<String> scopeMappings = new LinkedHashSet<>();
|
||||||
|
private List<String> defaultRoles = new LinkedList<>();
|
||||||
|
private boolean surrogateAuthRequired;
|
||||||
|
private String managementUrl;
|
||||||
|
private String rootUrl;
|
||||||
|
private String baseUrl;
|
||||||
|
private boolean bearerOnly;
|
||||||
|
private boolean consentRequired;
|
||||||
|
private boolean standardFlowEnabled;
|
||||||
|
private boolean implicitFlowEnabled;
|
||||||
|
private boolean directAccessGrantsEnabled;
|
||||||
|
private boolean serviceAccountsEnabled;
|
||||||
|
private int nodeReRegistrationTimeout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag signalizing that any of the setters has been meaningfully used.
|
||||||
|
*/
|
||||||
|
protected boolean updated;
|
||||||
|
|
||||||
|
protected AbstractClientEntity() {
|
||||||
|
this.id = null;
|
||||||
|
this.realmId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractClientEntity(K id, String realmId) {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
Objects.requireNonNull(realmId, "realmId");
|
||||||
|
|
||||||
|
this.id = id;
|
||||||
|
this.realmId = realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public K getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUpdated() {
|
||||||
|
return this.updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.updated |= ! Objects.equals(this.clientId, clientId);
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.updated |= ! Objects.equals(this.name, name);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.updated |= ! Objects.equals(this.description, description);
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getRedirectUris() {
|
||||||
|
return redirectUris;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRedirectUris(Set<String> redirectUris) {
|
||||||
|
this.updated |= ! Objects.equals(this.redirectUris, redirectUris);
|
||||||
|
this.redirectUris = redirectUris;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.updated |= ! Objects.equals(this.enabled, enabled);
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAlwaysDisplayInConsole() {
|
||||||
|
return alwaysDisplayInConsole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlwaysDisplayInConsole(boolean alwaysDisplayInConsole) {
|
||||||
|
this.updated |= ! Objects.equals(this.alwaysDisplayInConsole, alwaysDisplayInConsole);
|
||||||
|
this.alwaysDisplayInConsole = alwaysDisplayInConsole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientAuthenticatorType() {
|
||||||
|
return clientAuthenticatorType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientAuthenticatorType(String clientAuthenticatorType) {
|
||||||
|
this.updated |= ! Objects.equals(this.clientAuthenticatorType, clientAuthenticatorType);
|
||||||
|
this.clientAuthenticatorType = clientAuthenticatorType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSecret() {
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecret(String secret) {
|
||||||
|
this.updated |= ! Objects.equals(this.secret, secret);
|
||||||
|
this.secret = secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRegistrationToken() {
|
||||||
|
return registrationToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegistrationToken(String registrationToken) {
|
||||||
|
this.updated |= ! Objects.equals(this.registrationToken, registrationToken);
|
||||||
|
this.registrationToken = registrationToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProtocol() {
|
||||||
|
return protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProtocol(String protocol) {
|
||||||
|
this.updated |= ! Objects.equals(this.protocol, protocol);
|
||||||
|
this.protocol = protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttributes(Map<String, String> attributes) {
|
||||||
|
this.updated |= ! Objects.equals(this.attributes, attributes);
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getAuthFlowBindings() {
|
||||||
|
return authFlowBindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthFlowBindings(Map<String, String> authFlowBindings) {
|
||||||
|
this.updated |= ! Objects.equals(this.authFlowBindings, authFlowBindings);
|
||||||
|
this.authFlowBindings = authFlowBindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPublicClient() {
|
||||||
|
return publicClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPublicClient(boolean publicClient) {
|
||||||
|
this.updated |= ! Objects.equals(this.publicClient, publicClient);
|
||||||
|
this.publicClient = publicClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFullScopeAllowed() {
|
||||||
|
return fullScopeAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFullScopeAllowed(boolean fullScopeAllowed) {
|
||||||
|
this.updated |= ! Objects.equals(this.fullScopeAllowed, fullScopeAllowed);
|
||||||
|
this.fullScopeAllowed = fullScopeAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFrontchannelLogout() {
|
||||||
|
return frontchannelLogout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFrontchannelLogout(boolean frontchannelLogout) {
|
||||||
|
this.updated |= ! Objects.equals(this.frontchannelLogout, frontchannelLogout);
|
||||||
|
this.frontchannelLogout = frontchannelLogout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNotBefore() {
|
||||||
|
return notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotBefore(int notBefore) {
|
||||||
|
this.updated |= ! Objects.equals(this.notBefore, notBefore);
|
||||||
|
this.notBefore = notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getScope() {
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScope(Set<String> scope) {
|
||||||
|
this.updated |= ! Objects.equals(this.scope, scope);
|
||||||
|
this.scope.clear();
|
||||||
|
this.scope.addAll(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getWebOrigins() {
|
||||||
|
return webOrigins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWebOrigins(Set<String> webOrigins) {
|
||||||
|
this.updated |= ! Objects.equals(this.webOrigins, webOrigins);
|
||||||
|
this.webOrigins.clear();
|
||||||
|
this.webOrigins.addAll(webOrigins);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
|
||||||
|
Objects.requireNonNull(model.getId(), "protocolMapper.id");
|
||||||
|
updated = true;
|
||||||
|
this.protocolMappers.put(model.getId(), model);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<ProtocolMapperModel> getProtocolMappers() {
|
||||||
|
return protocolMappers.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateProtocolMapper(String id, ProtocolMapperModel mapping) {
|
||||||
|
updated = true;
|
||||||
|
protocolMappers.put(id, mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeProtocolMapper(String id) {
|
||||||
|
updated |= protocolMappers.remove(id) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProtocolMappers(Collection<ProtocolMapperModel> protocolMappers) {
|
||||||
|
this.updated |= ! Objects.equals(this.protocolMappers, protocolMappers);
|
||||||
|
this.protocolMappers.clear();
|
||||||
|
this.protocolMappers.putAll(protocolMappers.stream().collect(Collectors.toMap(ProtocolMapperModel::getId, Function.identity())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProtocolMapperModel getProtocolMapperById(String id) {
|
||||||
|
return id == null ? null : protocolMappers.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSurrogateAuthRequired() {
|
||||||
|
return surrogateAuthRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
|
||||||
|
this.updated |= ! Objects.equals(this.surrogateAuthRequired, surrogateAuthRequired);
|
||||||
|
this.surrogateAuthRequired = surrogateAuthRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getManagementUrl() {
|
||||||
|
return managementUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setManagementUrl(String managementUrl) {
|
||||||
|
this.updated |= ! Objects.equals(this.managementUrl, managementUrl);
|
||||||
|
this.managementUrl = managementUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRootUrl() {
|
||||||
|
return rootUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRootUrl(String rootUrl) {
|
||||||
|
this.updated |= ! Objects.equals(this.rootUrl, rootUrl);
|
||||||
|
this.rootUrl = rootUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBaseUrl() {
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBaseUrl(String baseUrl) {
|
||||||
|
this.updated |= ! Objects.equals(this.baseUrl, baseUrl);
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getDefaultRoles() {
|
||||||
|
return defaultRoles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultRoles(Collection<String> defaultRoles) {
|
||||||
|
this.updated |= ! Objects.equals(this.defaultRoles, defaultRoles);
|
||||||
|
this.defaultRoles.clear();
|
||||||
|
this.defaultRoles.addAll(defaultRoles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDefaultRole(String name) {
|
||||||
|
updated = true;
|
||||||
|
if (name != null) {
|
||||||
|
defaultRoles.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeDefaultRoles(String... defaultRoles) {
|
||||||
|
for (String defaultRole : defaultRoles) {
|
||||||
|
updated |= this.defaultRoles.remove(defaultRole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBearerOnly() {
|
||||||
|
return bearerOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBearerOnly(boolean bearerOnly) {
|
||||||
|
this.updated |= ! Objects.equals(this.bearerOnly, bearerOnly);
|
||||||
|
this.bearerOnly = bearerOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConsentRequired() {
|
||||||
|
return consentRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConsentRequired(boolean consentRequired) {
|
||||||
|
this.updated |= ! Objects.equals(this.consentRequired, consentRequired);
|
||||||
|
this.consentRequired = consentRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStandardFlowEnabled() {
|
||||||
|
return standardFlowEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStandardFlowEnabled(boolean standardFlowEnabled) {
|
||||||
|
this.updated |= ! Objects.equals(this.standardFlowEnabled, standardFlowEnabled);
|
||||||
|
this.standardFlowEnabled = standardFlowEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isImplicitFlowEnabled() {
|
||||||
|
return implicitFlowEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
|
||||||
|
this.updated |= ! Objects.equals(this.implicitFlowEnabled, implicitFlowEnabled);
|
||||||
|
this.implicitFlowEnabled = implicitFlowEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDirectAccessGrantsEnabled() {
|
||||||
|
return directAccessGrantsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
|
||||||
|
this.updated |= ! Objects.equals(this.directAccessGrantsEnabled, directAccessGrantsEnabled);
|
||||||
|
this.directAccessGrantsEnabled = directAccessGrantsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isServiceAccountsEnabled() {
|
||||||
|
return serviceAccountsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
|
||||||
|
this.updated |= ! Objects.equals(this.serviceAccountsEnabled, serviceAccountsEnabled);
|
||||||
|
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNodeReRegistrationTimeout() {
|
||||||
|
return nodeReRegistrationTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNodeReRegistrationTimeout(int nodeReRegistrationTimeout) {
|
||||||
|
this.updated |= ! Objects.equals(this.nodeReRegistrationTimeout, nodeReRegistrationTimeout);
|
||||||
|
this.nodeReRegistrationTimeout = nodeReRegistrationTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addWebOrigin(String webOrigin) {
|
||||||
|
updated = true;
|
||||||
|
this.webOrigins.add(webOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeWebOrigin(String webOrigin) {
|
||||||
|
updated |= this.webOrigins.remove(webOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addRedirectUri(String redirectUri) {
|
||||||
|
this.updated |= ! this.redirectUris.contains(redirectUri);
|
||||||
|
this.redirectUris.add(redirectUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeRedirectUri(String redirectUri) {
|
||||||
|
updated |= this.redirectUris.remove(redirectUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttribute(String name, String value) {
|
||||||
|
this.updated = true;
|
||||||
|
this.attributes.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAttribute(String name) {
|
||||||
|
this.updated |= this.attributes.remove(name) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAttribute(String name) {
|
||||||
|
return this.attributes.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthenticationFlowBindingOverride(String binding) {
|
||||||
|
return this.authFlowBindings.get(binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getAuthenticationFlowBindingOverrides() {
|
||||||
|
return this.authFlowBindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAuthenticationFlowBindingOverride(String binding) {
|
||||||
|
updated |= this.authFlowBindings.remove(binding) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticationFlowBindingOverride(String binding, String flowId) {
|
||||||
|
this.updated = true;
|
||||||
|
this.authFlowBindings.put(binding, flowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> getScopeMappings() {
|
||||||
|
return scopeMappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addScopeMapping(String id) {
|
||||||
|
if (id != null) {
|
||||||
|
updated = true;
|
||||||
|
scopeMappings.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteScopeMapping(String id) {
|
||||||
|
updated |= scopeMappings.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addClientScope(String id, boolean defaultScope) {
|
||||||
|
if (id != null) {
|
||||||
|
updated = true;
|
||||||
|
this.clientScopes.put(id, defaultScope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeClientScope(String id) {
|
||||||
|
if (id != null) {
|
||||||
|
updated |= clientScopes.remove(id) != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<String> getClientScopes(boolean defaultScope) {
|
||||||
|
return this.clientScopes.entrySet().stream()
|
||||||
|
.filter(me -> Objects.equals(me.getValue(), defaultScope))
|
||||||
|
.map(Entry::getKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRealmId() {
|
||||||
|
return this.realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.client;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientScopeModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public abstract class AbstractClientModel<E extends AbstractEntity> implements ClientModel {
|
||||||
|
|
||||||
|
protected final KeycloakSession session;
|
||||||
|
protected final RealmModel realm;
|
||||||
|
protected final E entity;
|
||||||
|
|
||||||
|
public AbstractClientModel(KeycloakSession session, RealmModel realm, E entity) {
|
||||||
|
Objects.requireNonNull(entity, "entity");
|
||||||
|
Objects.requireNonNull(realm, "realm");
|
||||||
|
|
||||||
|
this.session = session;
|
||||||
|
this.realm = realm;
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addClientScopes(Set<ClientScopeModel> clientScopes, boolean defaultScope) {
|
||||||
|
for (ClientScopeModel cs : clientScopes) {
|
||||||
|
addClientScope(cs, defaultScope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<RoleModel> getRealmScopeMappings() {
|
||||||
|
String realmId = realm.getId();
|
||||||
|
return getScopeMappingsStream()
|
||||||
|
.filter(rm -> Objects.equals(rm.getContainerId(), realmId))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RoleModel getRole(String name) {
|
||||||
|
return session.realms().getClientRole(realm, this, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RoleModel addRole(String name) {
|
||||||
|
return session.realms().addClientRole(realm, this, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RoleModel addRole(String id, String name) {
|
||||||
|
return session.realms().addClientRole(realm, this, id, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeRole(RoleModel role) {
|
||||||
|
return session.realms().removeRole(realm, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<RoleModel> getRoles() {
|
||||||
|
return session.realms().getClientRoles(realm, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<RoleModel> getRoles(Integer firstResult, Integer maxResults) {
|
||||||
|
return session.realms().getClientRoles(realm, this, firstResult, maxResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<RoleModel> searchForRoles(String search, Integer first, Integer max) {
|
||||||
|
return session.realms().searchForClientRoles(realm, this, search, first, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof ClientModel)) return false;
|
||||||
|
|
||||||
|
ClientModel that = (ClientModel) o;
|
||||||
|
return Objects.equals(that.getId(), getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getId().hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,540 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.client;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientScopeModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
import com.google.common.base.Functions;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public abstract class MapClientAdapter extends AbstractClientModel<MapClientEntity> implements ClientModel {
|
||||||
|
|
||||||
|
public MapClientAdapter(KeycloakSession session, RealmModel realm, MapClientEntity entity) {
|
||||||
|
super(session, realm, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return entity.getId().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientId() {
|
||||||
|
return entity.getClientId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
entity.setClientId(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return entity.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(String name) {
|
||||||
|
entity.setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return entity.getDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDescription(String description) {
|
||||||
|
entity.setDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return entity.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
entity.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlwaysDisplayInConsole() {
|
||||||
|
return entity.isAlwaysDisplayInConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlwaysDisplayInConsole(boolean alwaysDisplayInConsole) {
|
||||||
|
entity.setAlwaysDisplayInConsole(alwaysDisplayInConsole);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSurrogateAuthRequired() {
|
||||||
|
return entity.isSurrogateAuthRequired();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
|
||||||
|
entity.setSurrogateAuthRequired(surrogateAuthRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getWebOrigins() {
|
||||||
|
return entity.getWebOrigins();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWebOrigins(Set<String> webOrigins) {
|
||||||
|
entity.setWebOrigins(webOrigins);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addWebOrigin(String webOrigin) {
|
||||||
|
entity.addWebOrigin(webOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeWebOrigin(String webOrigin) {
|
||||||
|
entity.removeWebOrigin(webOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getRedirectUris() {
|
||||||
|
return entity.getRedirectUris();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRedirectUris(Set<String> redirectUris) {
|
||||||
|
entity.setRedirectUris(redirectUris);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRedirectUri(String redirectUri) {
|
||||||
|
entity.addRedirectUri(redirectUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeRedirectUri(String redirectUri) {
|
||||||
|
entity.removeRedirectUri(redirectUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getManagementUrl() {
|
||||||
|
return entity.getManagementUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setManagementUrl(String url) {
|
||||||
|
entity.setManagementUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRootUrl() {
|
||||||
|
return entity.getRootUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRootUrl(String url) {
|
||||||
|
entity.setRootUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBaseUrl() {
|
||||||
|
return entity.getBaseUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBaseUrl(String url) {
|
||||||
|
entity.setBaseUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBearerOnly() {
|
||||||
|
return entity.isBearerOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBearerOnly(boolean only) {
|
||||||
|
entity.setBearerOnly(only);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientAuthenticatorType() {
|
||||||
|
return entity.getClientAuthenticatorType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientAuthenticatorType(String clientAuthenticatorType) {
|
||||||
|
entity.setClientAuthenticatorType(clientAuthenticatorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validateSecret(String secret) {
|
||||||
|
return MessageDigest.isEqual(secret.getBytes(), entity.getSecret().getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSecret() {
|
||||||
|
return entity.getSecret();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSecret(String secret) {
|
||||||
|
entity.setSecret(secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNodeReRegistrationTimeout() {
|
||||||
|
return entity.getNodeReRegistrationTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNodeReRegistrationTimeout(int timeout) {
|
||||||
|
entity.setNodeReRegistrationTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRegistrationToken() {
|
||||||
|
return entity.getRegistrationToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegistrationToken(String registrationToken) {
|
||||||
|
entity.setRegistrationToken(registrationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProtocol() {
|
||||||
|
return entity.getProtocol();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProtocol(String protocol) {
|
||||||
|
entity.setProtocol(protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttribute(String name, String value) {
|
||||||
|
entity.setAttribute(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAttribute(String name) {
|
||||||
|
entity.removeAttribute(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAttribute(String name) {
|
||||||
|
return entity.getAttribute(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getAttributes() {
|
||||||
|
return entity.getAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthenticationFlowBindingOverride(String binding) {
|
||||||
|
return entity.getAuthenticationFlowBindingOverride(binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getAuthenticationFlowBindingOverrides() {
|
||||||
|
return entity.getAuthenticationFlowBindingOverrides();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAuthenticationFlowBindingOverride(String binding) {
|
||||||
|
entity.removeAuthenticationFlowBindingOverride(binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAuthenticationFlowBindingOverride(String binding, String flowId) {
|
||||||
|
entity.setAuthenticationFlowBindingOverride(binding, flowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFrontchannelLogout() {
|
||||||
|
return entity.isFrontchannelLogout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFrontchannelLogout(boolean flag) {
|
||||||
|
entity.setFrontchannelLogout(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFullScopeAllowed() {
|
||||||
|
return entity.isFullScopeAllowed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFullScopeAllowed(boolean value) {
|
||||||
|
entity.setFullScopeAllowed(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPublicClient() {
|
||||||
|
return entity.isPublicClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPublicClient(boolean flag) {
|
||||||
|
entity.setPublicClient(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConsentRequired() {
|
||||||
|
return entity.isConsentRequired();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setConsentRequired(boolean consentRequired) {
|
||||||
|
entity.setConsentRequired(consentRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStandardFlowEnabled() {
|
||||||
|
return entity.isStandardFlowEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStandardFlowEnabled(boolean standardFlowEnabled) {
|
||||||
|
entity.setStandardFlowEnabled(standardFlowEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isImplicitFlowEnabled() {
|
||||||
|
return entity.isImplicitFlowEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
|
||||||
|
entity.setImplicitFlowEnabled(implicitFlowEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDirectAccessGrantsEnabled() {
|
||||||
|
return entity.isDirectAccessGrantsEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
|
||||||
|
entity.setDirectAccessGrantsEnabled(directAccessGrantsEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isServiceAccountsEnabled() {
|
||||||
|
return entity.isServiceAccountsEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
|
||||||
|
entity.setServiceAccountsEnabled(serviceAccountsEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNotBefore() {
|
||||||
|
return entity.getNotBefore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNotBefore(int notBefore) {
|
||||||
|
entity.setNotBefore(notBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************** Client scopes ****************/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addClientScope(ClientScopeModel clientScope, boolean defaultScope) {
|
||||||
|
final String id = clientScope == null ? null : clientScope.getId();
|
||||||
|
if (id != null) {
|
||||||
|
entity.addClientScope(id, defaultScope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeClientScope(ClientScopeModel clientScope) {
|
||||||
|
final String id = clientScope == null ? null : clientScope.getId();
|
||||||
|
if (id != null) {
|
||||||
|
entity.removeClientScope(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope, boolean filterByProtocol) {
|
||||||
|
Stream<ClientScopeModel> res = this.entity.getClientScopes(defaultScope)
|
||||||
|
.map(realm::getClientScopeById)
|
||||||
|
.filter(Objects::nonNull);
|
||||||
|
|
||||||
|
if (filterByProtocol) {
|
||||||
|
String clientProtocol = getProtocol() == null ? OIDCLoginProtocol.LOGIN_PROTOCOL : getProtocol();
|
||||||
|
res = res.filter(cs -> Objects.equals(cs.getProtocol(), clientProtocol));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.collect(Collectors.toMap(ClientScopeModel::getName, Functions.identity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************** Scopes mappings ****************/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<RoleModel> getScopeMappingsStream() {
|
||||||
|
return this.entity.getScopeMappings().stream()
|
||||||
|
.map(realm::getRoleById)
|
||||||
|
.filter(Objects::nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addScopeMapping(RoleModel role) {
|
||||||
|
final String id = role == null ? null : role.getId();
|
||||||
|
if (id != null) {
|
||||||
|
this.entity.addScopeMapping(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteScopeMapping(RoleModel role) {
|
||||||
|
final String id = role == null ? null : role.getId();
|
||||||
|
if (id != null) {
|
||||||
|
this.entity.deleteScopeMapping(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasScope(RoleModel role) {
|
||||||
|
if (isFullScopeAllowed()) return true;
|
||||||
|
|
||||||
|
final String id = role == null ? null : role.getId();
|
||||||
|
if (id != null && this.entity.getScopeMappings().contains(id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getScopeMappingsStream().anyMatch(r -> r.hasRole(role))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<RoleModel> roles = getRoles();
|
||||||
|
if (roles.contains(role)) return true;
|
||||||
|
|
||||||
|
return roles.stream().anyMatch(r -> r.hasRole(role));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************** Default roles ****************/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getDefaultRoles() {
|
||||||
|
return entity.getDefaultRoles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addDefaultRole(String name) {
|
||||||
|
RoleModel role = getRole(name);
|
||||||
|
if (role == null) {
|
||||||
|
addRole(name);
|
||||||
|
}
|
||||||
|
this.entity.addDefaultRole(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeDefaultRoles(String... defaultRoles) {
|
||||||
|
this.entity.removeDefaultRoles(defaultRoles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************** Protocol mappers ****************/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ProtocolMapperModel> getProtocolMappers() {
|
||||||
|
return Collections.unmodifiableSet(new HashSet<>(entity.getProtocolMappers()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
|
||||||
|
if (model == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProtocolMapperModel pm = new ProtocolMapperModel();
|
||||||
|
pm.setId(KeycloakModelUtils.generateId());
|
||||||
|
pm.setName(model.getName());
|
||||||
|
pm.setProtocol(model.getProtocol());
|
||||||
|
pm.setProtocolMapper(model.getProtocolMapper());
|
||||||
|
|
||||||
|
if (model.getConfig() != null) {
|
||||||
|
pm.setConfig(new HashMap<>(model.getConfig()));
|
||||||
|
} else {
|
||||||
|
pm.setConfig(new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity.addProtocolMapper(pm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeProtocolMapper(ProtocolMapperModel mapping) {
|
||||||
|
final String id = mapping == null ? null : mapping.getId();
|
||||||
|
if (id != null) {
|
||||||
|
entity.removeProtocolMapper(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateProtocolMapper(ProtocolMapperModel mapping) {
|
||||||
|
final String id = mapping == null ? null : mapping.getId();
|
||||||
|
if (id != null) {
|
||||||
|
entity.updateProtocolMapper(id, mapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtocolMapperModel getProtocolMapperById(String id) {
|
||||||
|
return entity.getProtocolMapperById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
|
||||||
|
return entity.getProtocolMappers().stream()
|
||||||
|
.filter(pm -> Objects.equals(pm.getProtocol(), protocol) && Objects.equals(pm.getName(), name))
|
||||||
|
.findAny()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.client;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public class MapClientEntity extends AbstractClientEntity<UUID> {
|
||||||
|
|
||||||
|
protected MapClientEntity() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MapClientEntity(UUID id, String realmId) {
|
||||||
|
super(id, realmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,310 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.map.client;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientProvider;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel.ClientUpdatedEvent;
|
||||||
|
import org.keycloak.models.RealmProvider;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
|
import org.keycloak.models.map.common.Serialization;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class MapClientProvider implements ClientProvider {
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(MapClientProvider.class);
|
||||||
|
private static final Predicate<MapClientEntity> ALWAYS_FALSE = c -> { return false; };
|
||||||
|
private final KeycloakSession session;
|
||||||
|
final MapKeycloakTransaction<UUID, MapClientEntity> tx;
|
||||||
|
private final MapStorage<UUID, MapClientEntity> clientStore;
|
||||||
|
private final ConcurrentMap<UUID, ConcurrentMap<String, Integer>> clientRegisteredNodesStore;
|
||||||
|
|
||||||
|
private static final Comparator<MapClientEntity> COMPARE_BY_CLIENT_ID = new Comparator<MapClientEntity>() {
|
||||||
|
@Override
|
||||||
|
public int compare(MapClientEntity o1, MapClientEntity o2) {
|
||||||
|
String c1 = o1 == null ? null : o1.getClientId();
|
||||||
|
String c2 = o2 == null ? null : o2.getClientId();
|
||||||
|
return c1 == c2 ? 0
|
||||||
|
: c1 == null ? -1
|
||||||
|
: c2 == null ? 1
|
||||||
|
: c1.compareTo(c2);
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public MapClientProvider(KeycloakSession session, MapStorage<UUID, MapClientEntity> clientStore, ConcurrentMap<UUID, ConcurrentMap<String, Integer>> clientRegisteredNodesStore) {
|
||||||
|
this.session = session;
|
||||||
|
this.clientStore = clientStore;
|
||||||
|
this.clientRegisteredNodesStore = clientRegisteredNodesStore;
|
||||||
|
this.tx = new MapKeycloakTransaction<>(clientStore);
|
||||||
|
session.getTransactionManager().enlistAfterCompletion(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientUpdatedEvent clientUpdatedEvent(ClientModel c) {
|
||||||
|
return new RealmModel.ClientUpdatedEvent() {
|
||||||
|
@Override
|
||||||
|
public ClientModel getUpdatedClient() {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeycloakSession getKeycloakSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private MapClientEntity registerEntityForChanges(MapClientEntity origEntity) {
|
||||||
|
final MapClientEntity res = Serialization.from(origEntity);
|
||||||
|
tx.putIfChanged(origEntity.getId(), res, MapClientEntity::isUpdated);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Function<MapClientEntity, ClientModel> entityToAdapterFunc(RealmModel realm) {
|
||||||
|
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
|
||||||
|
|
||||||
|
return origEntity -> new MapClientAdapter(session, realm, registerEntityForChanges(origEntity)) {
|
||||||
|
@Override
|
||||||
|
public void updateClient() {
|
||||||
|
// commit
|
||||||
|
MapClientProvider.this.tx.replace(entity.getId(), this.entity);
|
||||||
|
session.getKeycloakSessionFactory().publish(clientUpdatedEvent(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This is runtime information and should have never been part of the adapter */
|
||||||
|
@Override
|
||||||
|
public Map<String, Integer> getRegisteredNodes() {
|
||||||
|
return clientRegisteredNodesStore.computeIfAbsent(entity.getId(), k -> new ConcurrentHashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerNode(String nodeHost, int registrationTime) {
|
||||||
|
Map<String, Integer> value = getRegisteredNodes();
|
||||||
|
value.put(nodeHost, registrationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregisterNode(String nodeHost) {
|
||||||
|
getRegisteredNodes().remove(nodeHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Predicate<MapClientEntity> entityRealmFilter(RealmModel realm) {
|
||||||
|
if (realm == null || realm.getId() == null) {
|
||||||
|
return MapClientProvider.ALWAYS_FALSE;
|
||||||
|
}
|
||||||
|
String realmId = realm.getId();
|
||||||
|
return entity -> Objects.equals(realmId, entity.getRealmId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ClientModel> getClients(RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||||
|
Stream<ClientModel> s = getClientsStream(realm);
|
||||||
|
if (firstResult >= 0) {
|
||||||
|
s = s.skip(firstResult);
|
||||||
|
}
|
||||||
|
if (maxResults >= 0) {
|
||||||
|
s = s.limit(maxResults);
|
||||||
|
}
|
||||||
|
return s.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream<MapClientEntity> getNotRemovedUpdatedClientsStream() {
|
||||||
|
Stream<MapClientEntity> updatedAndNotRemovedClientsStream = clientStore.entrySet().stream()
|
||||||
|
.map(tx::getUpdated) // If the client has been removed, tx.get will return null, otherwise it will return me.getValue()
|
||||||
|
.filter(Objects::nonNull);
|
||||||
|
return Stream.concat(tx.createdValuesStream(clientStore.keySet()), updatedAndNotRemovedClientsStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
public Stream<ClientModel> getClientsStream(RealmModel realm) {
|
||||||
|
return getNotRemovedUpdatedClientsStream()
|
||||||
|
.filter(entityRealmFilter(realm))
|
||||||
|
.sorted(COMPARE_BY_CLIENT_ID)
|
||||||
|
.map(entityToAdapterFunc(realm))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ClientModel> getClients(RealmModel realm) {
|
||||||
|
return getClientsStream(realm).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientModel addClient(RealmModel realm, String id, String clientId) {
|
||||||
|
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
|
||||||
|
|
||||||
|
if (clientId == null) {
|
||||||
|
clientId = entityId.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
MapClientEntity entity = new MapClientEntity(entityId, realm.getId());
|
||||||
|
entity.setClientId(clientId);
|
||||||
|
entity.setEnabled(true);
|
||||||
|
entity.setStandardFlowEnabled(true);
|
||||||
|
if (tx.get(entity.getId(), clientStore::get) != null) {
|
||||||
|
throw new ModelDuplicateException("Client exists: " + id);
|
||||||
|
}
|
||||||
|
tx.putIfAbsent(entity.getId(), entity);
|
||||||
|
final ClientModel resource = entityToAdapterFunc(realm).apply(entity);
|
||||||
|
|
||||||
|
// TODO: Sending an event should be extracted to store layer
|
||||||
|
session.getKeycloakSessionFactory().publish((RealmModel.ClientCreationEvent) () -> resource);
|
||||||
|
resource.updateClient(); // This is actualy strange contract - it should be the store code to call updateClient
|
||||||
|
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ClientModel> getAlwaysDisplayInConsoleClients(RealmModel realm) {
|
||||||
|
return getClientsStream(realm)
|
||||||
|
.filter(ClientModel::isAlwaysDisplayInConsole)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeClients(RealmModel realm) {
|
||||||
|
LOG.tracef("removeClients(%s)%s", realm, getShortStackTrace());
|
||||||
|
|
||||||
|
getClientsStream(realm)
|
||||||
|
.map(ClientModel::getId)
|
||||||
|
.collect(Collectors.toSet()) // This is necessary to read out all the client IDs before removing the clients
|
||||||
|
.forEach(cid -> removeClient(realm, cid));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeClient(RealmModel realm, String id) {
|
||||||
|
if (id == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Sending an event (and client role removal) should be extracted to store layer
|
||||||
|
final ClientModel client = getClientById(realm, id);
|
||||||
|
if (client == null) return false;
|
||||||
|
session.users().preRemove(realm, client);
|
||||||
|
final RealmProvider realms = session.realms();
|
||||||
|
for (RoleModel role : client.getRoles()) {
|
||||||
|
realms.removeRole(realm, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.getKeycloakSessionFactory().publish(new RealmModel.ClientRemovedEvent() {
|
||||||
|
@Override
|
||||||
|
public ClientModel getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeycloakSession getKeycloakSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// TODO: ^^^^^^^ Up to here
|
||||||
|
|
||||||
|
tx.remove(UUID.fromString(id));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getClientsCount(RealmModel realm) {
|
||||||
|
return this.getNotRemovedUpdatedClientsStream()
|
||||||
|
.filter(entityRealmFilter(realm))
|
||||||
|
.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientModel getClientById(RealmModel realm, String id) {
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
MapClientEntity entity = tx.get(UUID.fromString(id), clientStore::get);
|
||||||
|
return (entity == null || ! entityRealmFilter(realm).test(entity))
|
||||||
|
? null
|
||||||
|
: entityToAdapterFunc(realm).apply(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientModel getClientByClientId(RealmModel realm, String clientId) {
|
||||||
|
if (clientId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String clientIdLower = clientId.toLowerCase();
|
||||||
|
|
||||||
|
return getNotRemovedUpdatedClientsStream()
|
||||||
|
.filter(entityRealmFilter(realm))
|
||||||
|
.filter(entity -> entity.getClientId() != null && Objects.equals(entity.getClientId().toLowerCase(), clientIdLower))
|
||||||
|
.map(entityToAdapterFunc(realm))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ClientModel> searchClientsByClientId(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) {
|
||||||
|
if (clientId == null) {
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
String clientIdLower = clientId.toLowerCase();
|
||||||
|
Stream<MapClientEntity> s = getNotRemovedUpdatedClientsStream()
|
||||||
|
.filter(entityRealmFilter(realm))
|
||||||
|
.filter(entity -> entity.getClientId() != null && entity.getClientId().toLowerCase().contains(clientIdLower))
|
||||||
|
.sorted(COMPARE_BY_CLIENT_ID);
|
||||||
|
|
||||||
|
if (firstResult >= 0) {
|
||||||
|
s = s.skip(firstResult);
|
||||||
|
}
|
||||||
|
if (maxResults >= 0) {
|
||||||
|
s = s.limit(maxResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
.map(entityToAdapterFunc(realm))
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.client;
|
||||||
|
|
||||||
|
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||||
|
import org.keycloak.models.ClientProvider;
|
||||||
|
import org.keycloak.models.ClientProviderFactory;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||||
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public class MapClientProviderFactory extends AbstractMapProviderFactory<ClientProvider> implements ClientProviderFactory {
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<UUID, ConcurrentMap<String, Integer>> REGISTERED_NODES_STORE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private MapStorage<UUID, MapClientEntity> store;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
|
||||||
|
this.store = sp.getStorage("clients", UUID.class, MapClientEntity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientProvider create(KeycloakSession session) {
|
||||||
|
return new MapClientProvider(session, store, REGISTERED_NODES_STORE);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public interface AbstractEntity<K> {
|
||||||
|
|
||||||
|
K getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag signalizing that any of the setters has been meaningfully used.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean isUpdated();
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.common;
|
||||||
|
|
||||||
|
import org.keycloak.Config.Scope;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public abstract class AbstractMapProviderFactory<T extends Provider> implements ProviderFactory<T> {
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "map";
|
||||||
|
|
||||||
|
protected final Logger LOG = Logger.getLogger(getClass());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.common;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public class Serialization {
|
||||||
|
|
||||||
|
public static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
abstract class IgnoreUpdatedMixIn { @JsonIgnore public abstract boolean isUpdated(); }
|
||||||
|
|
||||||
|
static {
|
||||||
|
MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||||
|
MAPPER.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||||
|
MAPPER.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
|
||||||
|
MAPPER.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
|
||||||
|
|
||||||
|
MAPPER.addMixIn(AbstractEntity.class, IgnoreUpdatedMixIn.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static <T extends AbstractEntity> T from(T orig) {
|
||||||
|
if (orig == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Naive solution but will do.
|
||||||
|
final T res = MAPPER.readValue(MAPPER.writeValueAsBytes(orig), (Class<T>) orig.getClass());
|
||||||
|
return res;
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.storage;
|
||||||
|
|
||||||
|
import org.keycloak.Config.Scope;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import org.keycloak.models.map.common.Serialization;
|
||||||
|
import com.fasterxml.jackson.databind.JavaType;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public class ConcurrentHashMapStorageProvider implements MapStorageProvider {
|
||||||
|
|
||||||
|
private static class ConcurrentHashMapStorage<K, V> extends ConcurrentHashMap<K, V> implements MapStorage<K, V> {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String PROVIDER_ID = "concurrenthashmap";
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(ConcurrentHashMapStorageProvider.class);
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<String, ConcurrentHashMap<?,?>> storages = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private File storageDirectory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapStorageProvider create(KeycloakSession session) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Scope config) {
|
||||||
|
File f = new File(config.get("dir"));
|
||||||
|
try {
|
||||||
|
this.storageDirectory = f.exists()
|
||||||
|
? f
|
||||||
|
: Files.createTempDirectory("storage-map-chm-").toFile();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
this.storageDirectory = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
storages.forEach(this::storeMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeMap(String fileName, ConcurrentHashMap<?, ?> store) {
|
||||||
|
if (fileName != null) {
|
||||||
|
File f = getFile(fileName);
|
||||||
|
try {
|
||||||
|
if (storageDirectory != null && storageDirectory.exists()) {
|
||||||
|
LOG.debugf("Storing contents to %s", f.getCanonicalPath());
|
||||||
|
Serialization.MAPPER.writeValue(f, store.values());
|
||||||
|
} else {
|
||||||
|
LOG.debugf("Not storing contents of %s because directory %s does not exist", fileName, this.storageDirectory);
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <K, V extends AbstractEntity<K>> ConcurrentHashMapStorage<?, V> loadMap(String fileName, Class<V> valueType, EnumSet<Flag> flags) {
|
||||||
|
ConcurrentHashMapStorage<K, V> store = new ConcurrentHashMapStorage<>();
|
||||||
|
|
||||||
|
if (! flags.contains(Flag.INITIALIZE_EMPTY)) {
|
||||||
|
final File f = getFile(fileName);
|
||||||
|
if (f != null && f.exists()) {
|
||||||
|
try {
|
||||||
|
LOG.debugf("Restoring contents from %s", f.getCanonicalPath());
|
||||||
|
JavaType type = Serialization.MAPPER.getTypeFactory().constructCollectionType(List.class, valueType);
|
||||||
|
|
||||||
|
List<V> values = Serialization.MAPPER.readValue(f, type);
|
||||||
|
values.forEach((V mce) -> store.put(mce.getId(), mce));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <K, V extends AbstractEntity<K>> MapStorage<K, V> getStorage(String name, Class<K> keyType, Class<V> valueType, Flag... flags) {
|
||||||
|
EnumSet<Flag> f = flags == null || flags.length == 0 ? EnumSet.noneOf(Flag.class) : EnumSet.of(flags[0], flags);
|
||||||
|
return (MapStorage<K, V>) storages.computeIfAbsent(name, n -> loadMap(name, valueType, f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getFile(String fileName) {
|
||||||
|
return storageDirectory == null
|
||||||
|
? null
|
||||||
|
: new File(storageDirectory, "map-" + fileName + ".json");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.storage;
|
||||||
|
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public interface MapStorageProvider extends Provider, ProviderFactory<MapStorageProvider> {
|
||||||
|
|
||||||
|
public enum Flag {
|
||||||
|
INITIALIZE_EMPTY,
|
||||||
|
LOCAL
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a key-value storage
|
||||||
|
* @param <K> type of the primary key
|
||||||
|
* @param <V> type of the value
|
||||||
|
* @param name Name of the storage
|
||||||
|
* @param flags
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
<K, V extends AbstractEntity<K>> MapStorage<K, V> getStorage(String name, Class<K> keyType, Class<V> valueType, Flag... flags);
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.storage;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.keycloak.provider.Spi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public class MapStorageSpi implements Spi {
|
||||||
|
|
||||||
|
public static final String NAME = "mapStorage";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Provider> getProviderClass() {
|
||||||
|
return MapStorageProvider.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
|
return MapStorageProvider.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
#
|
||||||
|
# Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
# and other contributors as indicated by the @author tags.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
org.keycloak.models.map.client.MapClientProviderFactory
|
|
@ -0,0 +1,18 @@
|
||||||
|
#
|
||||||
|
# Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
# and other contributors as indicated by the @author tags.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
org.keycloak.models.map.storage.ConcurrentHashMapStorageProvider
|
|
@ -0,0 +1,18 @@
|
||||||
|
#
|
||||||
|
# Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
# and other contributors as indicated by the @author tags.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
org.keycloak.models.map.storage.MapStorageSpi
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
public interface ClientProviderFactory extends ProviderFactory<ClientProvider> {
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.keycloak.provider.Spi;
|
||||||
|
|
||||||
|
public class ClientSpi implements Spi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "client";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Provider> getProviderClass() {
|
||||||
|
return ClientProvider.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
|
return ClientProviderFactory.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -18,6 +18,7 @@
|
||||||
org.keycloak.provider.ExceptionConverterSpi
|
org.keycloak.provider.ExceptionConverterSpi
|
||||||
org.keycloak.storage.UserStorageProviderSpi
|
org.keycloak.storage.UserStorageProviderSpi
|
||||||
org.keycloak.storage.federated.UserFederatedStorageProviderSpi
|
org.keycloak.storage.federated.UserFederatedStorageProviderSpi
|
||||||
|
org.keycloak.models.ClientSpi
|
||||||
org.keycloak.models.RealmSpi
|
org.keycloak.models.RealmSpi
|
||||||
org.keycloak.models.ActionTokenStoreSpi
|
org.keycloak.models.ActionTokenStoreSpi
|
||||||
org.keycloak.models.CodeToTokenStoreSpi
|
org.keycloak.models.CodeToTokenStoreSpi
|
||||||
|
|
|
@ -35,6 +35,11 @@ public interface ClientModel extends ClientScopeModel, RoleContainerModel, Prot
|
||||||
String PUBLIC_KEY = "publicKey";
|
String PUBLIC_KEY = "publicKey";
|
||||||
String X509CERTIFICATE = "X509Certificate";
|
String X509CERTIFICATE = "X509Certificate";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the current state of the client immediately to the underlying store, similarly to a commit.
|
||||||
|
*
|
||||||
|
* @deprecated Do not use, to be removed
|
||||||
|
*/
|
||||||
void updateClient();
|
void updateClient();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,6 +19,10 @@ package org.keycloak.models;
|
||||||
|
|
||||||
import org.keycloak.provider.ProviderEvent;
|
import org.keycloak.provider.ProviderEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -53,7 +57,26 @@ public interface RoleContainerModel {
|
||||||
|
|
||||||
void addDefaultRole(String name);
|
void addDefaultRole(String name);
|
||||||
|
|
||||||
void updateDefaultRoles(String... defaultRoles);
|
default void updateDefaultRoles(String... defaultRoles) {
|
||||||
|
List<String> defaultRolesArray = Arrays.asList(defaultRoles);
|
||||||
|
Collection<String> entities = getDefaultRoles();
|
||||||
|
Set<String> already = new HashSet<>();
|
||||||
|
ArrayList<String> remove = new ArrayList<>();
|
||||||
|
for (String rel : entities) {
|
||||||
|
if (! defaultRolesArray.contains(rel)) {
|
||||||
|
remove.add(rel);
|
||||||
|
} else {
|
||||||
|
already.add(rel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removeDefaultRoles(remove.toArray(new String[] {}));
|
||||||
|
|
||||||
|
for (String roleName : defaultRoles) {
|
||||||
|
if (!already.contains(roleName)) {
|
||||||
|
addDefaultRole(roleName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void removeDefaultRoles(String... defaultRoles);
|
void removeDefaultRoles(String... defaultRoles);
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -25,14 +27,26 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public interface ScopeContainerModel {
|
public interface ScopeContainerModel {
|
||||||
|
|
||||||
Set<RoleModel> getScopeMappings();
|
@Deprecated
|
||||||
|
default Set<RoleModel> getScopeMappings() {
|
||||||
|
return getScopeMappingsStream().collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
default Stream<RoleModel> getScopeMappingsStream() {
|
||||||
|
return getScopeMappings().stream();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From the scope mappings returned by {@link #getScopeMappings()} returns only those
|
||||||
|
* that belong to the realm that owns this scope container.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Set<RoleModel> getRealmScopeMappings();
|
||||||
|
|
||||||
void addScopeMapping(RoleModel role);
|
void addScopeMapping(RoleModel role);
|
||||||
|
|
||||||
void deleteScopeMapping(RoleModel role);
|
void deleteScopeMapping(RoleModel role);
|
||||||
|
|
||||||
Set<RoleModel> getRealmScopeMappings();
|
|
||||||
|
|
||||||
boolean hasScope(RoleModel role);
|
boolean hasScope(RoleModel role);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,10 @@
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
<artifactId>guava</artifactId>
|
<artifactId>guava</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-model-map</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak.testsuite</groupId>
|
<groupId>org.keycloak.testsuite</groupId>
|
||||||
<artifactId>integration-arquillian-servers-app-server-spi</artifactId>
|
<artifactId>integration-arquillian-servers-app-server-spi</artifactId>
|
||||||
|
@ -160,6 +164,26 @@
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-antrun-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>process-test-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>run</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<target>
|
||||||
|
<delete>
|
||||||
|
<fileset dir="${project.build.directory}" includes="map-*.json"/>
|
||||||
|
</delete>
|
||||||
|
</target>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
|
|
@ -71,6 +71,7 @@ import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
@ -317,12 +318,12 @@ public class RealmTest extends AbstractAdminTest {
|
||||||
realm1 = adminClient.realms().realm("test-immutable").toRepresentation();
|
realm1 = adminClient.realms().realm("test-immutable").toRepresentation();
|
||||||
realm1.setRealm("test-immutable-old");
|
realm1.setRealm("test-immutable-old");
|
||||||
adminClient.realms().realm("test-immutable").update(realm1);
|
adminClient.realms().realm("test-immutable").update(realm1);
|
||||||
realm1 = adminClient.realms().realm("test-immutable-old").toRepresentation();
|
assertThat(adminClient.realms().realm("test-immutable-old").toRepresentation(), notNullValue());
|
||||||
|
|
||||||
RealmRepresentation realm2 = new RealmRepresentation();
|
RealmRepresentation realm2 = new RealmRepresentation();
|
||||||
realm2.setRealm("test-immutable");
|
realm2.setRealm("test-immutable");
|
||||||
adminClient.realms().create(realm2);
|
adminClient.realms().create(realm2);
|
||||||
realm2 = adminClient.realms().realm("test-immutable").toRepresentation();
|
assertThat(adminClient.realms().realm("test-immutable").toRepresentation(), notNullValue());
|
||||||
|
|
||||||
adminClient.realms().realm("test-immutable-old").remove();
|
adminClient.realms().realm("test-immutable-old").remove();
|
||||||
adminClient.realms().realm("test-immutable").remove();
|
adminClient.realms().realm("test-immutable").remove();
|
||||||
|
|
|
@ -44,6 +44,17 @@
|
||||||
"provider": "${keycloak.user.provider:jpa}"
|
"provider": "${keycloak.user.provider:jpa}"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"client": {
|
||||||
|
"provider": "${keycloak.client.provider:jpa}"
|
||||||
|
},
|
||||||
|
|
||||||
|
"mapStorage": {
|
||||||
|
"provider": "${keycloak.mapStorage.provider:concurrenthashmap}",
|
||||||
|
"concurrenthashmap": {
|
||||||
|
"dir": "${project.build.directory:target}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"userFederatedStorage": {
|
"userFederatedStorage": {
|
||||||
"provider": "${keycloak.userFederatedStorage.provider:jpa}"
|
"provider": "${keycloak.userFederatedStorage.provider:jpa}"
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,6 +18,17 @@
|
||||||
"provider": "${keycloak.realm.provider:}"
|
"provider": "${keycloak.realm.provider:}"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"client": {
|
||||||
|
"provider": "${keycloak.client.provider:jpa}"
|
||||||
|
},
|
||||||
|
|
||||||
|
"mapStorage": {
|
||||||
|
"provider": "${keycloak.mapStorage.provider:concurrenthashmap}",
|
||||||
|
"concurrenthashmap": {
|
||||||
|
"dir": "${project.build.directory:target}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"user": {
|
"user": {
|
||||||
"provider": "${keycloak.user.provider:}"
|
"provider": "${keycloak.user.provider:}"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue