org.cloudfoundry.identity.uaa.scim.dao.standard.JdbcScimUserProvisioning.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudfoundry.identity.uaa.scim.dao.standard.JdbcScimUserProvisioning.java

Source

/*******************************************************************************
 *     Cloud Foundry
 *     Copyright (c) [2009-2014] 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.dao.standard;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.rest.jdbc.AbstractQueryable;
import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory;
import org.cloudfoundry.identity.uaa.scim.dao.common.ScimSearchQueryConverter;
import org.cloudfoundry.identity.uaa.scim.dao.common.ScimUserProvisioning;
import org.cloudfoundry.identity.uaa.scim.domain.common.ScimMeta;
import org.cloudfoundry.identity.uaa.scim.domain.common.ScimUserInterface;
import org.cloudfoundry.identity.uaa.scim.domain.standard.ScimUser;
import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException;
import org.cloudfoundry.identity.uaa.scim.exception.InvalidScimResourceException;
import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceAlreadyExistsException;
import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceConstraintFailedException;
import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException;
import org.cloudfoundry.identity.uaa.scim.validate.DefaultPasswordValidator;
import org.cloudfoundry.identity.uaa.scim.validate.PasswordValidator;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;

/**
 * @author Luke Taylor
 * @author Dave Syer
 */
public class JdbcScimUserProvisioning extends AbstractQueryable<ScimUserInterface> implements ScimUserProvisioning {

    private final Log logger = LogFactory.getLog(getClass());

    public static final String USER_FIELDS = "id,version,created,lastModified,username,email,givenName,familyName,active,phoneNumber,verified";

    public static final String CREATE_USER_SQL = "insert into users (" + USER_FIELDS
            + ",password) values (?,?,?,?,?,?,?,?,?,?,?,?)";

    public static final String UPDATE_USER_SQL = "update users set version=?, lastModified=?, userName=?, email=?, givenName=?, familyName=?, active=?, phoneNumber=?, verified=? where id=? and version=?";

    public static final String DEACTIVATE_USER_SQL = "update users set active=? where id=?";

    public static final String VERIFY_USER_SQL = "update users set verified=? where id=?";

    public static final String DELETE_USER_SQL = "delete from users where id=?";

    public static final String ID_FOR_DELETED_USER_SQL = "select id from users where userName=? and active=false";

    public static final String CHANGE_PASSWORD_SQL = "update users set lastModified=?, password=? where id=?";

    public static final String READ_PASSWORD_SQL = "select password from users where id=?";

    public static final String USER_BY_ID_QUERY = "select " + USER_FIELDS + " from users " + "where id=?";

    public static final String ALL_usetre = "select " + USER_FIELDS + " from users";

    static final Pattern unquotedEq = Pattern.compile("(id|username|email|givenName|familyName) eq [^'^\"].*",
            Pattern.CASE_INSENSITIVE);

    protected final JdbcTemplate jdbcTemplate;

    private PasswordValidator passwordValidator = new DefaultPasswordValidator();

    private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    private boolean deactivateOnDelete = true;

    private final RowMapper<ScimUserInterface> mapper = new ScimUserRowMapper();

    private Pattern usernamePattern = Pattern.compile("[a-zA-Z0-9+\\-_.@]+");

    public JdbcScimUserProvisioning(JdbcTemplate jdbcTemplate, JdbcPagingListFactory pagingListFactory) {

        super(jdbcTemplate, pagingListFactory, new ScimUserRowMapper());
        Assert.notNull(jdbcTemplate);
        this.jdbcTemplate = jdbcTemplate;
        setQueryConverter(new ScimSearchQueryConverter());
    }

    @Override
    public ScimUserInterface retrieve(String id) {

        try {
            ScimUserInterface u = jdbcTemplate.queryForObject(USER_BY_ID_QUERY, mapper, id);
            return u;
        } catch (EmptyResultDataAccessException e) {
            throw new ScimResourceNotFoundException("User " + id + " does not exist");
        }
    }

    @Override
    protected String getBaseSqlQuery() {

        return ALL_usetre;
    }

    @Override
    public List<ScimUserInterface> retrieveAll() {

        return query("id pr", "created", true);
    }

    @Override
    public List<ScimUserInterface> query(String filter, String sortBy, boolean ascending) {

        if (unquotedEq.matcher(filter).matches()) {
            throw new IllegalArgumentException("Eq argument in filter must be quoted");
        }
        return super.query(filter, sortBy, ascending);
    }

    @Override
    public ScimUserInterface create(final ScimUserInterface user) {

        validate(user);
        logger.debug("Creating new user: " + user.getUserName());

        final String id = UUID.randomUUID().toString();
        try {
            jdbcTemplate.update(CREATE_USER_SQL, new PreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps) throws SQLException {

                    ps.setString(1, id);
                    ps.setInt(2, user.getVersion());
                    ps.setTimestamp(3, new Timestamp(new Date().getTime()));
                    ps.setTimestamp(4, new Timestamp(new Date().getTime()));
                    ps.setString(5, user.getUserName());
                    ps.setString(6, user.getPrimaryEmail());
                    ps.setString(7, user.getGivenName());
                    ps.setString(8, user.getFamilyName());
                    ps.setBoolean(9, user.isActive());
                    String phoneNumber = extractPhoneNumber(user);
                    ps.setString(10, phoneNumber);
                    ps.setBoolean(11, user.isVerified());
                    ps.setString(12, user.getPassword());
                }
            });
        } catch (DuplicateKeyException e) {
            throw new ScimResourceAlreadyExistsException(
                    "Username already in use (could be inactive account): " + user.getUserName());
        }
        return retrieve(id);
    }

    @Override
    public ScimUserInterface createUser(ScimUserInterface user, final String password)
            throws InvalidPasswordException, InvalidScimResourceException {

        passwordValidator.validate(password, user);
        user.setPassword(passwordEncoder.encode(password));
        return create(user);
    }

    private void validate(final ScimUserInterface user) throws InvalidScimResourceException {

        if (!usernamePattern.matcher(user.getUserName()).matches()) {
            throw new InvalidScimResourceException("Username must match pattern: " + usernamePattern.pattern());
        }
        if (user.getEmails() == null || user.getEmails().isEmpty()) {
            throw new InvalidScimResourceException("An email must be provided.");
        }
    }

    private String extractPhoneNumber(final ScimUserInterface user) {

        String phoneNumber = null;
        if (user.getPhoneNumbers() != null && !user.getPhoneNumbers().isEmpty()) {
            phoneNumber = user.getPhoneNumbers().get(0).getValue();
        }
        return phoneNumber;
    }

    @Override
    public ScimUserInterface update(final String id, final ScimUserInterface user)
            throws InvalidScimResourceException {

        validate(user);
        logger.debug("Updating user " + user.getUserName());

        int updated = jdbcTemplate.update(UPDATE_USER_SQL, new PreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps) throws SQLException {

                ps.setInt(1, user.getVersion() + 1);
                ps.setTimestamp(2, new Timestamp(new Date().getTime()));
                ps.setString(3, user.getUserName());
                ps.setString(4, user.getPrimaryEmail());
                ps.setString(5, user.getGivenName());
                ps.setString(6, user.getFamilyName());
                ps.setBoolean(7, user.isActive());
                ps.setString(8, extractPhoneNumber(user));
                ps.setBoolean(9, user.isVerified());
                ps.setString(10, id);
                ps.setInt(11, user.getVersion());
            }
        });
        ScimUserInterface result = retrieve(id);
        if (updated == 0) {
            throw new OptimisticLockingFailureException(
                    String.format("Attempt to update a user (%s) with wrong version: expected=%d but found=%d", id,
                            result.getVersion(), user.getVersion()));
        }
        if (updated > 1) {
            throw new IncorrectResultSizeDataAccessException(1);
        }
        return result;
    }

    @Override
    public boolean changePassword(final String id, String oldPassword, final String newPassword)
            throws ScimResourceNotFoundException {

        if (oldPassword != null) {
            checkPasswordMatches(id, oldPassword);
        }
        passwordValidator.validate(newPassword, retrieve(id));
        final String encNewPassword = passwordEncoder.encode(newPassword);
        int updated = jdbcTemplate.update(CHANGE_PASSWORD_SQL, new PreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps) throws SQLException {

                ps.setTimestamp(1, new Timestamp(new Date().getTime()));
                ps.setString(2, encNewPassword);
                ps.setString(3, id);
            }
        });
        if (updated == 0) {
            throw new ScimResourceNotFoundException("User " + id + " does not exist");
        }
        if (updated != 1) {
            throw new ScimResourceConstraintFailedException("User " + id + " duplicated");
        }
        return true;
    }

    // Checks the existing password for a user
    private void checkPasswordMatches(String id, String oldPassword) {

        String currentPassword;
        try {
            currentPassword = jdbcTemplate.queryForObject(READ_PASSWORD_SQL, new Object[] { id },
                    new int[] { Types.VARCHAR }, String.class);
        } catch (IncorrectResultSizeDataAccessException e) {
            throw new ScimResourceNotFoundException("User " + id + " does not exist");
        }

        if (!passwordEncoder.matches(oldPassword, currentPassword)) {
            throw new BadCredentialsException("Old password is incorrect");
        }
    }

    @Override
    public ScimUserInterface delete(String id, int version) {

        ScimUserInterface user = retrieve(id);
        return deactivateOnDelete ? deactivateUser(user, version) : deleteUser(user, version);
    }

    private ScimUserInterface deactivateUser(ScimUserInterface user, int version) {

        logger.debug("Deactivating user: " + user.getId());
        int updated;
        if (version < 0) {
            // Ignore
            updated = jdbcTemplate.update(DEACTIVATE_USER_SQL, false, user.getId());
        } else {
            updated = jdbcTemplate.update(DEACTIVATE_USER_SQL + " and version=?", false, user.getId(), version);
        }
        if (updated == 0) {
            throw new OptimisticLockingFailureException(
                    String.format("Attempt to update a user (%s) with wrong version: expected=%d but found=%d",
                            user.getId(), user.getVersion(), version));
        }
        if (updated > 1) {
            throw new IncorrectResultSizeDataAccessException(1);
        }
        user.setActive(false);
        return user;
    }

    @Override
    public ScimUserInterface verifyUser(String id, int version)
            throws ScimResourceNotFoundException, InvalidScimResourceException {

        logger.debug("Verifying user: " + id);
        int updated;
        if (version < 0) {
            // Ignore
            updated = jdbcTemplate.update(VERIFY_USER_SQL, true, id);
        } else {
            updated = jdbcTemplate.update(VERIFY_USER_SQL + " and version=?", true, id, version);
        }
        ScimUserInterface user = retrieve(id);
        if (updated == 0) {
            throw new OptimisticLockingFailureException(
                    String.format("Attempt to update a user (%s) with wrong version: expected=%d but found=%d",
                            user.getId(), user.getVersion(), version));
        }
        if (updated > 1) {
            throw new IncorrectResultSizeDataAccessException(1);
        }
        return user;
    }

    private ScimUserInterface deleteUser(ScimUserInterface user, int version) {

        logger.debug("Deleting user: " + user.getId());
        int updated;

        if (version < 0) {
            updated = jdbcTemplate.update(DELETE_USER_SQL, user.getId());
        } else {
            updated = jdbcTemplate.update(DELETE_USER_SQL + " and version=?", user.getId(), version);
        }
        if (updated == 0) {
            throw new OptimisticLockingFailureException(
                    String.format("Attempt to update a user (%s) with wrong version: expected=%d but found=%d",
                            user.getId(), user.getVersion(), version));
        }
        return user;
    }

    public void setDeactivateOnDelete(boolean deactivateOnDelete) {

        this.deactivateOnDelete = deactivateOnDelete;
    }

    public void setPasswordValidator(PasswordValidator passwordValidator) {

        Assert.notNull(passwordValidator, "passwordValidator cannot be null");
        this.passwordValidator = passwordValidator;
    }

    /**
     * The encoder used to hash passwords before storing them in the database.
     * <p/>
     * Defaults to a {@link BCryptPasswordEncoder}.
     */
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {

        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
        this.passwordEncoder = passwordEncoder;
    }

    /**
     * Sets the regular expression which will be used to validate the username.
     */
    public void setUsernamePattern(String usernamePattern) {

        Assert.hasText(usernamePattern, "Username pattern must not be empty");
        this.usernamePattern = Pattern.compile(usernamePattern);
    }

    private static final class ScimUserRowMapper implements RowMapper<ScimUserInterface> {

        @Override
        public ScimUserInterface mapRow(ResultSet rs, int rowNum) throws SQLException {

            String id = rs.getString(1);
            int version = rs.getInt(2);
            Date created = rs.getTimestamp(3);
            Date lastModified = rs.getTimestamp(4);
            String userName = rs.getString(5);
            String email = rs.getString(6);
            String givenName = rs.getString(7);
            String familyName = rs.getString(8);
            boolean active = rs.getBoolean(9);
            String phoneNumber = rs.getString(10);
            boolean verified = rs.getBoolean(11);
            ScimUserInterface user = new ScimUser();
            user.setId(id);
            ScimMeta meta = new ScimMeta();
            meta.setVersion(version);
            meta.setCreated(created);
            meta.setLastModified(lastModified);
            user.setMeta(meta);
            user.setUserName(userName);
            user.addEmail(email);
            if (phoneNumber != null) {
                user.addPhoneNumber(phoneNumber);
            }

            user.setGivenName(givenName);
            user.setFamilyName(familyName);
            user.setActive(active);
            user.setVerified(verified);
            return user;
        }
    }
}