/* * Copyright 2015 Smartling, Inc. * * 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 com.smartling.keycloak.provider; import com.smartling.keycloak.federation.FederatedUserModel; import com.smartling.keycloak.federation.FederatedUserService; import com.smartling.keycloak.federation.UserCredentialsDto; import org.apache.http.HttpStatus; import org.jboss.logging.Logger; import org.jboss.resteasy.client.jaxrs.ResteasyClient; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import javax.ws.rs.NotFoundException; import javax.ws.rs.core.Response; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; /** * Remote API based user federation provider. * * @author Scott Rossillo */ public class RemoteUserFederationProvider implements UserFederationProvider { private static final Logger LOG = Logger.getLogger(RemoteUserFederationProvider.class); private static final Set<String> supportedCredentialTypes = Collections.singleton(UserCredentialModel.PASSWORD); private KeycloakSession session; private UserFederationProviderModel model; private final FederatedUserService federatedUserService; private static FederatedUserService buildClient(String uri) { ResteasyClient client = new ResteasyClientBuilder().disableTrustManager().build(); ResteasyWebTarget target = client.target(uri); return target .proxyBuilder(FederatedUserService.class) .classloader(FederatedUserService.class.getClassLoader()) .build(); } public RemoteUserFederationProvider(KeycloakSession session, UserFederationProviderModel model, String uri) { this(session, model, buildClient(uri)); LOG.debugf("Using validation base URI: " + uri); } protected RemoteUserFederationProvider(KeycloakSession session, UserFederationProviderModel model, FederatedUserService federatedUserService) { this.session = session; this.model = model; this.federatedUserService = federatedUserService; } @Override public boolean synchronizeRegistrations() { return false; } @Override public UserModel register(RealmModel realm, UserModel user) { LOG.warn("User registration not supported."); return null; } @Override public boolean removeUser(RealmModel realm, UserModel user) { return true; } private UserModel createUserModel(RealmModel realm, String rawUsername) throws NotFoundException { String username = rawUsername.toLowerCase().trim(); FederatedUserModel remoteUser = federatedUserService.getUserDetails(username); LOG.infof("Creating user model for: %s", username); UserModel userModel = session.userStorage().addUser(realm, username); if (!username.equals(remoteUser.getEmail())) { throw new IllegalStateException(String.format("Local and remote users differ: [%s != %s]", username, remoteUser.getUsername())); } userModel.setFederationLink(model.getId()); userModel.setEnabled(remoteUser.isEnabled()); userModel.setEmail(username); userModel.setEmailVerified(remoteUser.isEmailVerified()); userModel.setFirstName(remoteUser.getFirstName()); userModel.setLastName(remoteUser.getLastName()); if (remoteUser.getAttributes() != null) { Map<String, List<String>> attributes = remoteUser.getAttributes(); for (String attributeName : attributes.keySet()) userModel.setAttribute(attributeName, attributes.get(attributeName)); } if (remoteUser.getRoles() != null) { for (String role : remoteUser.getRoles()) { RoleModel roleModel = realm.getRole(role); if (roleModel != null) { userModel.grantRole(roleModel); LOG.infof("Granted user %s, role %s", username, role); } } } return userModel; } @Override public UserModel getUserByUsername(RealmModel realm, String username) { LOG.infof("Get by username: %s", username); try { return this.createUserModel(realm, username); } catch (NotFoundException ex) { LOG.errorf("Federated user not found: %s", username); return null; } } @Override public UserModel getUserByEmail(RealmModel realm, String email) { LOG.infof("Get by email: %s", email); try { return this.createUserModel(realm, email); } catch (NotFoundException ex) { LOG.error("Federated user (by email) not found: " + email); return null; } } @Override public List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) { LOG.debug("In searchByAttributes(): " + attributes); return Collections.emptyList(); } @Override public void preRemove(RealmModel realm) { // no-op } @Override public void preRemove(RealmModel realm, RoleModel role) { // no-op } @Override public void preRemove(RealmModel realm, GroupModel group) { // no-op } @Override public Set<String> getSupportedCredentialTypes(UserModel user) { return supportedCredentialTypes; } @Override public Set<String> getSupportedCredentialTypes() { return supportedCredentialTypes; } @Override public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) { LOG.infof("Validating credentials for %s", user.getUsername()); if (input == null || input.isEmpty()) { throw new IllegalArgumentException("UserCredentialModel list is empty or null!"); } UserCredentialModel credentials = input.get(0); Response response = federatedUserService.validateLogin(user.getUsername(), new UserCredentialsDto(credentials.getValue())); boolean valid = HttpStatus.SC_OK == response.getStatus(); if (valid) { user.updateCredential(credentials); user.setFederationLink(null); } return valid; } @Override public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) { return validCredentials(realm, user, Arrays.asList(input)); } @Override public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) { return CredentialValidationOutput.failed(); } @Override public UserModel validateAndProxy(RealmModel realm, UserModel local) { return local; } @Override public boolean isValid(RealmModel realm, UserModel local) { LOG.debugf("Checking if user is valid: %s", local.getUsername()); Response response = federatedUserService.validateUserExists(local.getUsername()); LOG.infof("Checked if %s is valid: %d", local.getUsername(), response.getStatus()); return HttpStatus.SC_OK == response.getStatus(); } @Override public void close() { // no-op } }