/*
* 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.services.managers;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.jboss.logging.Logger;
import org.keycloak.authentication.ClientAuthenticator;
import org.keycloak.authentication.ClientAuthenticatorFactory;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserManager;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocolFactory;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
import org.keycloak.representations.adapters.config.BaseRealmConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.sessions.AuthenticationSessionProvider;
import java.net.URI;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ClientManager {
private static final Logger logger = Logger.getLogger(ClientManager.class);
protected RealmManager realmManager;
public ClientManager(RealmManager realmManager) {
this.realmManager = realmManager;
}
public ClientManager() {
}
/**
* Should not be called from an import. This really expects that the client is created from the admin console.
*
* @param session
* @param realm
* @param rep
* @param addDefaultRoles
* @return
*/
public static ClientModel createClient(KeycloakSession session, RealmModel realm, ClientRepresentation rep, boolean addDefaultRoles) {
ClientModel client = RepresentationToModel.createClient(session, realm, rep, addDefaultRoles);
if (rep.getProtocol() != null) {
LoginProtocolFactory providerFactory = (LoginProtocolFactory) session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, rep.getProtocol());
providerFactory.setupClientDefaults(rep, client);
}
// remove default mappers if there is a template
if (rep.getProtocolMappers() == null && rep.getClientTemplate() != null) {
Set<ProtocolMapperModel> mappers = client.getProtocolMappers();
for (ProtocolMapperModel mapper : mappers) client.removeProtocolMapper(mapper);
}
return client;
}
public boolean removeClient(RealmModel realm, ClientModel client) {
if (realm.removeClient(client.getId())) {
UserSessionProvider sessions = realmManager.getSession().sessions();
if (sessions != null) {
sessions.onClientRemoved(realm, client);
}
UserSessionPersisterProvider sessionsPersister = realmManager.getSession().getProvider(UserSessionPersisterProvider.class);
if (sessionsPersister != null) {
sessionsPersister.onClientRemoved(realm, client);
}
AuthenticationSessionProvider authSessions = realmManager.getSession().authenticationSessions();
if (authSessions != null) {
authSessions.onClientRemoved(realm, client);
}
UserModel serviceAccountUser = realmManager.getSession().users().getServiceAccount(client);
if (serviceAccountUser != null) {
new UserManager(realmManager.getSession()).removeUser(realm, serviceAccountUser);
}
return true;
} else {
return false;
}
}
public Set<String> validateRegisteredNodes(ClientModel client) {
Map<String, Integer> registeredNodes = client.getRegisteredNodes();
if (registeredNodes == null || registeredNodes.isEmpty()) {
return Collections.emptySet();
}
int currentTime = Time.currentTime();
Set<String> validatedNodes = new TreeSet<String>();
if (client.getNodeReRegistrationTimeout() > 0) {
List<String> toRemove = new LinkedList<String>();
for (Map.Entry<String, Integer> entry : registeredNodes.entrySet()) {
Integer lastReRegistration = entry.getValue();
if (lastReRegistration + client.getNodeReRegistrationTimeout() < currentTime) {
toRemove.add(entry.getKey());
} else {
validatedNodes.add(entry.getKey());
}
}
// Remove time-outed nodes
for (String node : toRemove) {
client.unregisterNode(node);
}
} else {
// Periodic node reRegistration is disabled, so allow all nodes
validatedNodes.addAll(registeredNodes.keySet());
}
return validatedNodes;
}
public void enableServiceAccount(ClientModel client) {
client.setServiceAccountsEnabled(true);
// Add dedicated user for this service account
if (realmManager.getSession().users().getServiceAccount(client) == null) {
String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + client.getClientId();
logger.debugf("Creating service account user '%s'", username);
// Don't use federation for service account user
UserModel user = realmManager.getSession().userLocalStorage().addUser(client.getRealm(), username);
user.setEnabled(true);
user.setEmail(username + "@placeholder.org");
user.setServiceAccountClientLink(client.getId());
}
// Add protocol mappers to retrieve clientId in access token
if (client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER) == null) {
logger.debugf("Creating service account protocol mapper '%s' for client '%s'", ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER, client.getClientId());
ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER,
ServiceAccountConstants.CLIENT_ID,
ServiceAccountConstants.CLIENT_ID, "String",
false, "",
true, true);
client.addProtocolMapper(protocolMapper);
}
// Add protocol mappers to retrieve hostname and IP address of client in access token
if (client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_HOST_PROTOCOL_MAPPER) == null) {
logger.debugf("Creating service account protocol mapper '%s' for client '%s'", ServiceAccountConstants.CLIENT_HOST_PROTOCOL_MAPPER, client.getClientId());
ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(ServiceAccountConstants.CLIENT_HOST_PROTOCOL_MAPPER,
ServiceAccountConstants.CLIENT_HOST,
ServiceAccountConstants.CLIENT_HOST, "String",
false, "",
true, true);
client.addProtocolMapper(protocolMapper);
}
if (client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ADDRESS_PROTOCOL_MAPPER) == null) {
logger.debugf("Creating service account protocol mapper '%s' for client '%s'", ServiceAccountConstants.CLIENT_ADDRESS_PROTOCOL_MAPPER, client.getClientId());
ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(ServiceAccountConstants.CLIENT_ADDRESS_PROTOCOL_MAPPER,
ServiceAccountConstants.CLIENT_ADDRESS,
ServiceAccountConstants.CLIENT_ADDRESS, "String",
false, "",
true, true);
client.addProtocolMapper(protocolMapper);
}
}
public void clientIdChanged(ClientModel client, String newClientId) {
logger.debugf("Updating clientId from '%s' to '%s'", client.getClientId(), newClientId);
UserModel serviceAccountUser = realmManager.getSession().users().getServiceAccount(client);
if (serviceAccountUser != null) {
String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + newClientId;
serviceAccountUser.setUsername(username);
serviceAccountUser.setEmail(username + "@placeholder.org");
}
}
@JsonPropertyOrder({"realm", "realm-public-key", "bearer-only", "auth-server-url", "ssl-required",
"resource", "public-client", "credentials",
"use-resource-role-mappings"})
public static class InstallationAdapterConfig extends BaseRealmConfig {
@JsonProperty("resource")
protected String resource;
@JsonProperty("use-resource-role-mappings")
protected Boolean useResourceRoleMappings;
@JsonProperty("bearer-only")
protected Boolean bearerOnly;
@JsonProperty("public-client")
protected Boolean publicClient;
@JsonProperty("credentials")
protected Map<String, Object> credentials;
@JsonProperty("policy-enforcer")
protected PolicyEnforcerConfig enforcerConfig;
public Boolean isUseResourceRoleMappings() {
return useResourceRoleMappings;
}
public void setUseResourceRoleMappings(Boolean useResourceRoleMappings) {
this.useResourceRoleMappings = useResourceRoleMappings;
}
public String getResource() {
return resource;
}
public void setResource(String resource) {
this.resource = resource;
}
public Map<String, Object> getCredentials() {
return credentials;
}
public void setCredentials(Map<String, Object> credentials) {
this.credentials = credentials;
}
public Boolean getPublicClient() {
return publicClient;
}
public void setPublicClient(Boolean publicClient) {
this.publicClient = publicClient;
}
public Boolean getBearerOnly() {
return bearerOnly;
}
public void setBearerOnly(Boolean bearerOnly) {
this.bearerOnly = bearerOnly;
}
public PolicyEnforcerConfig getEnforcerConfig() {
return this.enforcerConfig;
}
public void setEnforcerConfig(PolicyEnforcerConfig enforcerConfig) {
this.enforcerConfig = enforcerConfig;
}
}
public InstallationAdapterConfig toInstallationRepresentation(RealmModel realmModel, ClientModel clientModel, URI baseUri) {
InstallationAdapterConfig rep = new InstallationAdapterConfig();
rep.setAuthServerUrl(baseUri.toString());
rep.setRealm(realmModel.getName());
rep.setSslRequired(realmModel.getSslRequired().name().toLowerCase());
if (clientModel.isPublicClient() && !clientModel.isBearerOnly()) rep.setPublicClient(true);
if (clientModel.isBearerOnly()) rep.setBearerOnly(true);
if (clientModel.getRoles().size() > 0) rep.setUseResourceRoleMappings(true);
rep.setResource(clientModel.getClientId());
if (showClientCredentialsAdapterConfig(clientModel)) {
Map<String, Object> adapterConfig = getClientCredentialsAdapterConfig(clientModel);
rep.setCredentials(adapterConfig);
}
return rep;
}
public String toJBossSubsystemConfig(RealmModel realmModel, ClientModel clientModel, URI baseUri) {
StringBuffer buffer = new StringBuffer();
buffer.append("<secure-deployment name=\"WAR MODULE NAME.war\">\n");
buffer.append(" <realm>").append(realmModel.getName()).append("</realm>\n");
buffer.append(" <auth-server-url>").append(baseUri.toString()).append("</auth-server-url>\n");
if (clientModel.isBearerOnly()){
buffer.append(" <bearer-only>true</bearer-only>\n");
} else if (clientModel.isPublicClient()) {
buffer.append(" <public-client>true</public-client>\n");
}
buffer.append(" <ssl-required>").append(realmModel.getSslRequired().name()).append("</ssl-required>\n");
buffer.append(" <resource>").append(clientModel.getClientId()).append("</resource>\n");
String cred = clientModel.getSecret();
if (showClientCredentialsAdapterConfig(clientModel)) {
Map<String, Object> adapterConfig = getClientCredentialsAdapterConfig(clientModel);
for (Map.Entry<String, Object> entry : adapterConfig.entrySet()) {
buffer.append(" <credential name=\"" + entry.getKey() + "\">");
Object value = entry.getValue();
if (value instanceof Map) {
buffer.append("\n");
Map<String, Object> asMap = (Map<String, Object>) value;
for (Map.Entry<String, Object> credEntry : asMap.entrySet()) {
buffer.append(" <" + credEntry.getKey() + ">" + credEntry.getValue().toString() + "</" + credEntry.getKey() + ">\n");
}
buffer.append(" </credential>\n");
} else {
buffer.append(value.toString()).append("</credential>\n");
}
}
}
if (clientModel.getRoles().size() > 0) {
buffer.append(" <use-resource-role-mappings>true</use-resource-role-mappings>\n");
}
buffer.append("</secure-deployment>\n");
return buffer.toString();
}
private boolean showClientCredentialsAdapterConfig(ClientModel client) {
if (client.isPublicClient()) {
return false;
}
if (client.isBearerOnly() && client.getNodeReRegistrationTimeout() <= 0) {
return false;
}
return true;
}
private Map<String, Object> getClientCredentialsAdapterConfig(ClientModel client) {
String clientAuthenticator = client.getClientAuthenticatorType();
ClientAuthenticatorFactory authenticator = (ClientAuthenticatorFactory) realmManager.getSession().getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, clientAuthenticator);
return authenticator.getAdapterConfiguration(client);
}
}