All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cloudfoundry.identity.uaa.scim.bootstrap.ScimUserBootstrap Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 *     Cloud Foundry
 *     Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved.
 *
 *     This product is licensed to you under the Apache License, Version 2.0 (the "License").
 *     You may not use this product except in compliance with the License.
 *
 *     This product includes a number of subcomponents with
 *     separate copyright notices and license terms. Your use of these
 *     subcomponents is subject to the terms and conditions of the
 *     subcomponent's license, as noted in the LICENSE file.
 *******************************************************************************/
package org.cloudfoundry.identity.uaa.scim.bootstrap;

import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent;
import org.cloudfoundry.identity.uaa.authentication.SystemAuthentication;
import org.cloudfoundry.identity.uaa.authentication.manager.AuthEvent;
import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent;
import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent;
import org.cloudfoundry.identity.uaa.authentication.manager.NewUserAuthenticatedEvent;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.scim.ScimGroup;
import org.cloudfoundry.identity.uaa.scim.ScimGroupMember;
import org.cloudfoundry.identity.uaa.scim.ScimGroupMembershipManager;
import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning;
import org.cloudfoundry.identity.uaa.scim.ScimUser;
import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning;
import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException;
import org.cloudfoundry.identity.uaa.scim.exception.MemberAlreadyExistsException;
import org.cloudfoundry.identity.uaa.scim.exception.MemberNotFoundException;
import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException;
import org.cloudfoundry.identity.uaa.user.UaaUser;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;

import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.util.StringUtils.hasText;
import static org.springframework.util.StringUtils.isEmpty;
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;

/**
 * Convenience class for provisioning user accounts from {@link UaaUser}
 * instances.
 *
 * @author Luke Taylor
 * @author Dave Syer
 */
public class ScimUserBootstrap implements
    InitializingBean, ApplicationListener, ApplicationEventPublisherAware {

    private static final Log logger = LogFactory.getLog(ScimUserBootstrap.class);

    private final ScimUserProvisioning scimUserProvisioning;

    private final ScimGroupProvisioning scimGroupProvisioning;

    private final ScimGroupMembershipManager membershipManager;

    private boolean override = false;

    private final Collection users;

    private List usersToDelete;

    private ApplicationEventPublisher publisher;

    /**
     * Flag to indicate that user accounts can be updated as well as created.
     *
     * @param override the override flag to set (default false)
     */
    public void setOverride(boolean override) {
        this.override = override;
    }

    public boolean isOverride() {
        return override;
    }

    public ScimUserBootstrap(ScimUserProvisioning scimUserProvisioning,
                             ScimGroupProvisioning scimGroupProvisioning,
                             ScimGroupMembershipManager membershipManager,
                             Collection users) {
        Assert.notNull(scimUserProvisioning, "scimUserProvisioning cannot be null");
        Assert.notNull(scimGroupProvisioning, "scimGroupProvisioning cannont be null");
        Assert.notNull(membershipManager, "memberShipManager cannot be null");
        Assert.notNull(users, "users list cannot be null");
        this.scimUserProvisioning = scimUserProvisioning;
        this.scimGroupProvisioning = scimGroupProvisioning;
        this.membershipManager = membershipManager;
        this.users = Collections.unmodifiableCollection(users);
    }

    public void setUsersToDelete(List usersToDelete) {
        this.usersToDelete = usersToDelete;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        List users = new LinkedList(ofNullable(this.users).orElse(emptyList()));
        List deleteMe = ofNullable(usersToDelete).orElse(emptyList());
        users.removeIf(u -> deleteMe.contains(u.getUsername()));
        for (UaaUser u : users) {
            u.setVerified(true);
            addUser(u);
        }
    }

    public void deleteUsers(@NotNull  List deleteList) throws Exception {
        if (deleteList.size()==0) {
            return;
        }
        StringBuilder filter = new StringBuilder();
        for (int i = deleteList.size()-1; i>=0; i--) {
            filter.append("username eq \"");
            filter.append(deleteList.get(i));
            filter.append("\"");
            if (i>0) {
                filter.append(" or ");
            }
        }
        List list = scimUserProvisioning.query("origin eq \"uaa\" and (" + filter.toString() + ")", IdentityZoneHolder.get().getId());
        for (ScimUser delete : list) {
            publish(new EntityDeletedEvent<>(delete, SystemAuthentication.SYSTEM_AUTHENTICATION));
        }
    }

    protected ScimUser getScimUser(UaaUser user) {
        List users = scimUserProvisioning.query("userName eq \"" + user.getUsername() + "\"" +
            " and origin eq \"" +
            (user.getOrigin() == null ? OriginKeys.UAA : user.getOrigin()) + "\"", IdentityZoneHolder.get().getId());

        if (users.isEmpty() && StringUtils.hasText(user.getId())) {
            try {
                users = Arrays.asList(scimUserProvisioning.retrieve(user.getId(), IdentityZoneHolder.get().getId()));
            } catch (ScimResourceNotFoundException x) {
                logger.debug("Unable to find scim user based on ID:"+user.getId());
            }
        }
        return users.isEmpty()?null:users.get(0);
    }

    /**
     * Add a user account from the properties provided.
     *
     * @param user a UaaUser
     */
    protected void addUser(UaaUser user) {
        ScimUser scimUser = getScimUser(user);
        if (scimUser==null) {
            if (isEmpty(user.getPassword()) && user.getOrigin().equals(OriginKeys.UAA)) {
                logger.debug("User's password cannot be empty");
                throw new InvalidPasswordException("Password cannot be empty", BAD_REQUEST);
            }
            createNewUser(user);
        }
        else {
            if (override) {
                updateUser(scimUser, user);
            } else {
                logger.debug("Override flag not set. Not registering existing user: " + user);
            }
        }
    }

    private void updateUser(ScimUser existingUser, UaaUser updatedUser) {
        updateUser(existingUser, updatedUser, true);
    }

    private void updateUser(ScimUser existingUser, UaaUser updatedUser, boolean updateGroups) {
        String id = existingUser.getId();
        logger.debug("Updating user account: " + updatedUser + " with SCIM Id: " + id);
        if (updateGroups) {
            logger.debug("Removing existing group memberships ...");
            Set existingGroups = membershipManager.getGroupsWithMember(id, true, IdentityZoneHolder.get().getId());

            for (ScimGroup g : existingGroups) {
                removeFromGroup(id, g.getDisplayName());
            }
        }

        final ScimUser newScimUser = convertToScimUser(updatedUser);
        newScimUser.setVersion(existingUser.getVersion());
        scimUserProvisioning.update(id, newScimUser, IdentityZoneHolder.get().getId());
        if (OriginKeys.UAA.equals(newScimUser.getOrigin()) && hasText(updatedUser.getPassword())) { //password is not relevant for non UAA users
            scimUserProvisioning.changePassword(id, null, updatedUser.getPassword(), IdentityZoneHolder.get().getId());
        }
        if (updateGroups) {
            Collection newGroups = convertToGroups(updatedUser.getAuthorities());
            logger.debug("Adding new groups " + newGroups);
            addGroups(id, newGroups);
        }
    }

    private void createNewUser(UaaUser user) {
        logger.debug("Registering new user account: " + user);
        ScimUser newScimUser = scimUserProvisioning.createUser(convertToScimUser(user), user.getPassword(), IdentityZoneHolder.get().getId());
        addGroups(newScimUser.getId(), convertToGroups(user.getAuthorities()));
    }

    private void addGroups(String scimUserid, Collection groups) {
        for (String group : groups) {
            addToGroup(scimUserid, group);
        }
    }

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof AuthEvent) {
            onApplicationEvent((AuthEvent)event);
        } else if (event instanceof ContextRefreshedEvent) {
            List deleteMe = ofNullable(usersToDelete).orElse(emptyList());
            try {
                //we do delete users here, because only now are all components started
                //and ready to receive events
                deleteUsers(deleteMe);
            } catch (Exception e) {
                logger.warn("Unable to delete users from manifest.", e);
                throw new RuntimeException(e);
            }
        }
    }

    public void onApplicationEvent(AuthEvent event) {
        UaaUser uaaUser = event.getUser();
        if (event instanceof InvitedUserAuthenticatedEvent) {
            ScimUser user = getScimUser(uaaUser);
            // external users should default to not being verified
            if (!OriginKeys.UAA.equals(uaaUser.getOrigin())) {
                uaaUser.setVerified(false);
            }
            updateUser(user, uaaUser, false);
            return;
        }
        if (event instanceof ExternalGroupAuthorizationEvent) {
            ExternalGroupAuthorizationEvent exEvent = (ExternalGroupAuthorizationEvent)event;
            //delete previous membership relation ships
            String origin = exEvent.getUser().getOrigin();
            if (!OriginKeys.UAA.equals(origin)) {
                Set groupsWithMember = membershipManager.getGroupsWithExternalMember(exEvent.getUser().getId(), origin);
                Map groupsMap = groupsWithMember.stream().collect(Collectors.toMap(ScimGroup::getDisplayName, Function.identity()));
                Collection externalAuthorities = exEvent.getExternalAuthorities();
                for (GrantedAuthority authority : externalAuthorities) {
                    if (groupsMap.containsKey(authority.getAuthority())) {
                        groupsMap.remove(authority.getAuthority());
                    } else {
                        addToGroup(exEvent.getUser().getId(), authority.getAuthority(), origin, exEvent.isAddGroups());
                    }
                }
                for (ScimGroup group : groupsMap.values()) {
                    membershipManager.removeMemberById(group.getId(), exEvent.getUser().getId(), group.getZoneId());
                }
            }
            //update the user itself
            if(event.isUserModified()) {
                //update the user itself
                ScimUser user = getScimUser(uaaUser);
                updateUser(user, uaaUser, false);
            }
            return;
        }

        if (event instanceof NewUserAuthenticatedEvent) {
            addUser(uaaUser);
            return;
        }
    }

    private void addToGroup(String scimUserId, String gName) {
        addToGroup(scimUserId,gName, OriginKeys.UAA, true);
    }

    private void addToGroup(String scimUserId, String gName, String origin, boolean addGroup) {
        if (!StringUtils.hasText(gName)) {
            return;
        }
        logger.debug("Adding to group: " + gName);
        List g = scimGroupProvisioning.query(String.format("displayName eq \"%s\"", gName), IdentityZoneHolder.get().getId());
        ScimGroup group;
        if ((g == null || g.isEmpty()) && (!addGroup)) {
            logger.debug("No group found with name:"+gName+". Group membership will not be added.");
            return;
        } else if (g == null || g.isEmpty()) {
            group = new ScimGroup(null,gName,IdentityZoneHolder.get().getId());
            group = scimGroupProvisioning.create(group, IdentityZoneHolder.get().getId());
        } else {
            group = g.get(0);
        }
        try {
            ScimGroupMember groupMember = new ScimGroupMember(scimUserId);
            groupMember.setOrigin(origin);
            membershipManager.addMember(group.getId(), groupMember, IdentityZoneHolder.get().getId());
        } catch (MemberAlreadyExistsException ex) {
            // do nothing
        }
    }

    private void removeFromGroup(String scimUserId, String gName) {
        if (!StringUtils.hasText(gName)) {
            return;
        }
        logger.debug("Removing membership of group: " + gName);
        List g = scimGroupProvisioning.query(String.format("displayName eq \"%s\"", gName), IdentityZoneHolder.get().getId());
        ScimGroup group;
        if (g == null || g.isEmpty()) {
            return;
        }
        else {
            group = g.get(0);
        }
        try {
            membershipManager.removeMemberById(group.getId(), scimUserId, IdentityZoneHolder.get().getId());
        } catch (MemberNotFoundException ex) {
            // do nothing
        }
    }

    /**
     * Convert UaaUser to SCIM data.
     *
     */
    private ScimUser convertToScimUser(UaaUser user) {
        ScimUser scim = new ScimUser(user.getId(), user.getUsername(), user.getGivenName(), user.getFamilyName());
        scim.addPhoneNumber(user.getPhoneNumber());
        scim.addEmail(user.getEmail());
        scim.setOrigin(user.getOrigin());
        scim.setExternalId(user.getExternalId());
        scim.setVerified(user.isVerified());
        return scim;
    }

    /**
     * Convert authorities to group names.
     */
    private Collection convertToGroups(List authorities) {
        List groups = new ArrayList();
        for (GrantedAuthority authority : authorities) {
            groups.add(authority.getAuthority());
        }
        return groups;
    }

    public void publish(ApplicationEvent event) {
        if (publisher!=null) {
            publisher.publishEvent(event);
        }
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        publisher = applicationEventPublisher;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy