ro.nextreports.server.security.ExternalAuthenticationProvider.java Source code

Java tutorial

Introduction

Here is the source code for ro.nextreports.server.security.ExternalAuthenticationProvider.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 ro.nextreports.server.security;

import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import ro.nextreports.server.StorageConstants;
import ro.nextreports.server.dao.StorageDao;
import ro.nextreports.server.domain.Entity;
import ro.nextreports.server.domain.Group;
import ro.nextreports.server.domain.User;
import ro.nextreports.server.exception.DuplicationException;
import ro.nextreports.server.exception.NotFoundException;
import ro.nextreports.server.util.StorageUtil;

/**
 * @author Decebal Suiu
 */
public abstract class ExternalAuthenticationProvider
        implements AuthenticationProvider, UserSynchronizer, InitializingBean {

    private static final Logger LOG = LoggerFactory.getLogger(ExternalAuthenticationProvider.class);

    private String realm;
    private StorageDao storageDao;
    protected ExternalUsersService externalUsersService;
    protected PlatformTransactionManager transactionManager;

    protected TransactionTemplate transactionTemplate;

    public String getRealm() {
        return realm;
    }

    @Required
    public void setRealm(String realm) {
        this.realm = realm;
    }

    @Required
    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    @Required
    public void setStorageDao(StorageDao storageDao) {
        this.storageDao = storageDao;
    }

    @Required
    public void setExternalUsersService(ExternalUsersService externalUsersService) {
        this.externalUsersService = externalUsersService;
    }

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if (!canResolve(authentication)) {
            return null; // it's ok to return null to ignore/skip the provider (see ProviderManager javadocs)
        }

        String username = authentication.getName();
        if (LOG.isDebugEnabled()) {
            LOG.debug("Trying to authenticate user '{}' via {}", username, realm);
        }

        try {
            authentication = doAuthenticate(authentication);
        } catch (AuthenticationException e) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Failed to authenticate user {} via {}: {}",
                        new Object[] { username, realm, e.getMessage() });
            }
            throw e;
        } catch (Exception e) {
            String message = "Unexpected exception in " + realm + " authentication:";
            LOG.error(message, e);
            throw new AuthenticationServiceException(message, e);
        }

        if (!authentication.isAuthenticated()) {
            return authentication;
        }

        // user authenticated
        if (LOG.isDebugEnabled()) {
            LOG.debug("'{}' authenticated successfully by {}.", username, realm);
        }

        User user = (User) authentication.getPrincipal();
        applyPatch(user);
        createOrUpdateUser(user);

        /*
        // create new authentication response containing the user and it's authorities
        NextServerAuthentication authenticationToken = new NextServerAuthentication(user, authentication.getCredentials());
            
        return authenticationToken;
        */

        return authentication;
    }

    public boolean supports(Class authentication) {
        if (NextServerAuthentication.class.isAssignableFrom(authentication)) {
            return true;
        } else if (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)) {
            return true;
        } else {
            return false;
        }
    }

    public void afterPropertiesSet() throws Exception {
        transactionTemplate = new TransactionTemplate(transactionManager);
    }

    //   @Profile // a proxy of the instance will be created and the test "provider instanceof ExternalAuthenticationProvider"
    // from NextServerSession doesn't work
    public void syncUsers(boolean createUsers, boolean deleteUsers) {
        List<String> realmUserNames = externalUsersService.getUserNames();
        if (LOG.isDebugEnabled()) {
            LOG.debug("Synchronize for realm=" + realm + " users=" + realmUserNames.size() + " (createUsers = "
                    + createUsers + ", deleteUsers = " + deleteUsers + ")");
        }

        for (String username : realmUserNames) {
            User user = externalUsersService.getUser(username);
            applyPatch(user);
            if (createUsers) {
                createOrUpdateUser(user);
            } else if (userExists(user.getUsername())) {
                updateUser(user);
            }

            List<String> groupNames = externalUsersService.getGroupNames(username);
            updateUserGroups(username, groupNames);
        }

        if (deleteUsers) {
            deleteAsyncUsers(realmUserNames);
        }
    }

    protected abstract Authentication doAuthenticate(Authentication authentication) throws AuthenticationException;

    protected void createOrUpdateUser(final User user) {
        if (userExists(user.getUsername())) {
            updateUser(user);
        } else {
            createUser(user);
        }
    }

    protected void createUser(final User user) {
        if (LOG.isDebugEnabled()) {
            LOG.debug(String.format("Creating new user '%s' for %s", user.getUsername(), realm));
        }

        transactionTemplate.execute(new TransactionCallbackWithoutResult() {

            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                try {
                    user.setCreatedBy("synchronizer");
                    String id = storageDao.addEntity(user);
                    user.setId(id);
                } catch (DuplicationException e) {
                    // never happening
                    throw new RuntimeException(e);
                }
            }

        });

    }

    protected void updateUser(final User user) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {

            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                try {
                    User nextUser = (User) storageDao.getEntity(user.getPath());
                    user.setId(nextUser.getId());

                    if (!isEquals(nextUser, user)) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug(String.format("Updating user '%s' for %s", user.getUsername(), realm));
                        }

                        user.setCreatedBy(nextUser.getCreatedBy());
                        user.setCreatedDate(nextUser.getCreatedDate());
                        user.setLastUpdatedBy("synchronizer");

                        storageDao.modifyEntity(user);
                    }
                } catch (NotFoundException e) {
                    // never happening
                    throw new RuntimeException(e);
                }
            }

        });
    }

    protected boolean userExists(final String username) {
        return (Boolean) transactionTemplate.execute(new TransactionCallback() {

            public Object doInTransaction(TransactionStatus transactionStatus) {
                return storageDao.entityExists(getUsernamePath(username));
            }

        });
    }

    protected void deleteAsyncUsers(final List<String> realmUserNames) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {

            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                // TODO improve performance
                try {
                    Entity[] users = storageDao.getEntityChildren(StorageConstants.USERS_ROOT);
                    for (int i = 0; i < users.length; i++) {
                        User user = (User) users[i];
                        if (realm.equals(user.getRealm()) && !realmUserNames
                                .contains(StringUtils.removeEnd(user.getUsername(), "@" + realm))) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug(String.format("Deleting async user '%s' for %s", user.getUsername(),
                                        realm));
                            }

                            storageDao.removeEntityById(user.getId());
                        }
                    }
                } catch (NotFoundException e) {
                    // never happening
                    throw new RuntimeException(e);
                }
            }

        });
    }

    protected void applyPatch(User user) {
        user.setUsername(user.getName() + "@" + realm);
        user.setRealm(realm);
        user.setPath(StorageUtil.createPath(StorageConstants.USERS_ROOT, user.getName()));
    }

    protected void updateUserGroups(final String username, final List<String> groupNames) {
        final String internalUsername = username + "@" + realm;
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {

            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                // TODO improve performance
                try {
                    for (String groupName : groupNames) {
                        if (!groupExists(groupName)) {
                            // create group and add the user as memeber
                            Group group = new Group(groupName, getGroupNamePath(groupName));
                            group.setCreatedBy("synchronizer");
                            LOG.debug("Create group '" + groupName + "'");
                            group.addMember(internalUsername);
                            LOG.debug("Add '" + internalUsername + "' as member of '" + groupName + "'");
                            storageDao.addEntity(group);
                        } else {
                            Group group = (Group) storageDao.getEntity(getGroupNamePath(groupName));
                            if (!group.isMember(internalUsername)) {
                                group.addMember(internalUsername);
                                LOG.debug("Add '" + internalUsername + "' as member of '" + groupName + "'");
                                group.setLastUpdatedBy("synchronizer");
                                storageDao.modifyEntity(group);
                            }
                        }
                    }
                } catch (DuplicationException e) {
                    // never happening
                    throw new RuntimeException(e);
                } catch (NotFoundException e) {
                    // never happening
                    throw new RuntimeException(e);
                }
            }

        });
    }

    protected boolean groupExists(final String groupName) {
        return (Boolean) transactionTemplate.execute(new TransactionCallback() {

            public Object doInTransaction(TransactionStatus transactionStatus) {
                return storageDao.entityExists(getGroupNamePath(groupName));
            }

        });
    }

    private boolean canResolve(Authentication authentication) {
        try {
            String realm = ((NextServerAuthentication) authentication).getRealm();
            if (StringUtils.isEmpty(realm) || !realm.equals(this.realm)) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("{} cannot resolve your request", this.realm);
                }

                return false;
            }
        } catch (ClassCastException e) {
            // ignore (probably it's a UsernamePasswordAuthenticationToken from CAS)
        }

        return true;
    }

    private boolean isEquals(User user1, User user2) {
        EqualsBuilder equalsBuilder = new EqualsBuilder();
        equalsBuilder.append(user1.getRealName(), user2.getRealName());
        equalsBuilder.append(user1.getPassword(), user2.getPassword());
        equalsBuilder.append(user1.getEmail(), user2.getEmail());
        equalsBuilder.append(user1.isAdmin(), user2.isAdmin());
        equalsBuilder.append(user1.isEnabled(), user2.isEnabled());
        equalsBuilder.append(user1.getProfile(), user2.getProfile());

        return equalsBuilder.isEquals();
    }

    private String getUsernamePath(String username) {
        return StorageUtil.createPath(StorageConstants.USERS_ROOT, username);
    }

    private String getGroupNamePath(String groupName) {
        return StorageUtil.createPath(StorageConstants.GROUPS_ROOT, groupName);
    }

}