services.plugins.subversion.subv1.SubversionPluginRunner.java Source code

Java tutorial

Introduction

Here is the source code for services.plugins.subversion.subv1.SubversionPluginRunner.java

Source

/*! LICENSE
 *
 * Copyright (c) 2015, The Agile Factory SA and/or its affiliates. All rights
 * reserved.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package services.plugins.subversion.subv1;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.IOUtils;

import constants.IMafConstants;
import constants.MafDataType;
import framework.commons.DataType;
import framework.commons.message.EventMessage;
import framework.commons.message.EventMessage.MessageType;
import framework.commons.message.SystemLevelRoleTypeEventMessage;
import framework.commons.message.UserEventMessage;
import framework.security.ISecurityService;
import framework.services.account.AccountManagementException;
import framework.services.account.DefaultAuthenticationAccountWriterPlugin;
import framework.services.account.IAccountManagerPlugin;
import framework.services.account.IAuthenticationAccountWriterPlugin;
import framework.services.account.IUserAccount;
import framework.services.plugins.api.IPluginActionDescriptor;
import framework.services.plugins.api.IPluginContext;
import framework.services.plugins.api.IPluginContext.LogLevel;
import framework.services.plugins.api.IPluginMenuDescriptor;
import framework.services.plugins.api.IPluginRunner;
import framework.services.plugins.api.PluginException;
import framework.utils.Utilities;
import models.framework_models.account.Principal;
import models.framework_models.account.SystemLevelRoleType;
import models.framework_models.account.SystemPermission;

/**
 * A plugin which manages the connection to an SVN server.<br/>
 * This "SVN server" must be implemented as an apache server with the
 * mod_dav_svn and mod_ldap for the authentication.<br/>
 * This plugin is able to "provision" the LDAP server to create additional
 * accounts.<br/>
 * Two groups are defined and must be configured on the server (see examples in
 * BizDock documentation):
 * <ul>
 * <li>subversion.developer.ldap.group : any user with the permission
 * SCM_DEVELOPER_PERMISSION will be set in this group.</li>
 * <li>subversion.delivery.manager.ldap.group : any user with the permission
 * SCM_ADMIN_PERMISSION will be set in this group. Admins are users who need to
 * access specific "restricted" configuration modules.</li>
 * </ul>
 * 
 * @author Pierre-Yves Cloux
 * 
 */
public class SubversionPluginRunner implements IPluginRunner {
    static final String MAIN_CONFIGURATION_NAME = "config";
    static final String LDAP_URL_PROPERTY = "ldap.url";
    static final String LDAP_USER_PROPERTY = "ldap.user";
    static final String LDAP_PASSWORD_PROPERTY = "ldap.password";
    static final String SUBVERSION_GUI_URL_PROPERTY = "subversion.gui.url";
    static final String SUBVERSION_DEVELOPER_LDAP_GROUP_PROPERTY = "subversion.developer.ldap.group";
    static final String SUBVERSION_DELIVERY_MANAGER_LDAP_GROUP_PROPERTY = "subversion.delivery.manager.ldap.group";

    private IPluginContext pluginContext;
    private ISecurityService securityService;
    private IAccountManagerPlugin accountManagerPlugin;
    private IAuthenticationAccountWriterPlugin authWriterPlugin;

    private String developerLdapGroup;
    private String deliveryManagerLdapGroup;
    private String subversionGuiUrl;

    /**
     * List of manual actions supported by this plugin.
     * 
     * @author Pierre-Yves Cloux
     */
    public static enum ActionMessage {
        RESYNC_ALL_ACTION;
    }

    /**
     * The actions implemented by the plugin see {@link ActionMessage}
     */
    private static Map<String, IPluginActionDescriptor> pluginActions = Collections
            .synchronizedMap(new HashMap<String, IPluginActionDescriptor>() {
                private static final long serialVersionUID = 1L;

                {
                    this.put(ActionMessage.RESYNC_ALL_ACTION.name(), new IPluginActionDescriptor() {

                        @Override
                        public Object getPayLoad(Long id) {
                            return ActionMessage.RESYNC_ALL_ACTION;
                        }

                        @Override
                        public String getLabel() {
                            return "Resync with LDAP";
                        }

                        @Override
                        public String getIdentifier() {
                            return ActionMessage.RESYNC_ALL_ACTION.name();
                        }

                        @Override
                        public DataType getDataType() {
                            return MafDataType.getUser();
                        }

                        @Override
                        public Object getPayLoad(Long arg0, Map<String, Object> arg1) {
                            throw new UnsupportedOperationException();
                        }
                    });
                }
            });

    /**
     * Default constructor.
     */
    @Inject
    public SubversionPluginRunner(IPluginContext pluginContext, ISecurityService securityService,
            IAccountManagerPlugin accountManagerPlugin) {
        this.pluginContext = pluginContext;
        this.securityService = securityService;
        this.accountManagerPlugin = accountManagerPlugin;
    }

    @Override
    public void start() throws PluginException {
        PropertiesConfiguration properties = getPluginContext().getPropertiesConfigurationFromByteArray(
                getPluginContext().getConfigurationAndMergeWithDefault(getPluginContext().getPluginDescriptor()
                        .getConfigurationBlockDescriptors().get(MAIN_CONFIGURATION_NAME)));
        properties.setThrowExceptionOnMissing(true);
        this.authWriterPlugin = new DefaultAuthenticationAccountWriterPlugin(
                properties.getString(LDAP_URL_PROPERTY), properties.getString(LDAP_USER_PROPERTY),
                properties.getString(LDAP_PASSWORD_PROPERTY));
        this.developerLdapGroup = properties.getString(SUBVERSION_DEVELOPER_LDAP_GROUP_PROPERTY);
        this.deliveryManagerLdapGroup = properties.getString(SUBVERSION_DELIVERY_MANAGER_LDAP_GROUP_PROPERTY);
        this.subversionGuiUrl = properties.getString(SUBVERSION_GUI_URL_PROPERTY);
        getPluginContext().log(LogLevel.INFO, "Subversion plugin started");
    }

    @Override
    public void stop() {
        getPluginContext().log(LogLevel.INFO, "Subversion plugin started");
    }

    @Override
    public synchronized void handleOutProvisioningMessage(EventMessage eventMessage) throws PluginException {
        getPluginContext().log(LogLevel.INFO,
                "Receive notification for handling event " + eventMessage.getTransactionId());
        getPluginContext().log(LogLevel.DEBUG, "Receive notification for handling event " + eventMessage);
        try {
            // Deal with Roles modifications
            if (eventMessage.getDataType().equals(MafDataType.getSystemLevelRoleType())
                    && eventMessage.getMessageType().equals(MessageType.OBJECT_UPDATED)
                    && eventMessage.getPayload() != null
                    && eventMessage.getPayload() instanceof SystemLevelRoleTypeEventMessage.PayLoad) {
                checkImpactOfSystemLevelRoleUpdate(eventMessage);
            }

            // Deal with User data notifications
            if (eventMessage.getDataType().equals(MafDataType.getUser())) {
                // Find the user account associated with this id
                // Get the user account
                IUserAccount userAccount = null;
                switch (eventMessage.getMessageType()) {
                case OBJECT_CREATED:
                    userAccount = getUserAccountFromInternalId(eventMessage.getInternalId());
                    notifyAccountCreated(eventMessage.getTransactionId(), userAccount);
                    break;
                case OBJECT_STATUS_CHANGED:
                    break;
                case OBJECT_DELETED:
                    if (eventMessage instanceof UserEventMessage
                            && eventMessage.getPayload() instanceof UserEventMessage.PayLoad
                            && eventMessage.getPayload() != null) {
                        notifyAccountDeleted(eventMessage.getTransactionId(),
                                ((UserEventMessage.PayLoad) eventMessage.getPayload()).getDeletedUid());
                    } else {
                        getPluginContext().log(LogLevel.ERROR, "Invalid event type received " + eventMessage);
                    }
                    break;
                case OBJECT_UPDATED:
                    userAccount = getUserAccountFromInternalId(eventMessage.getInternalId());
                    notifyAccountUpdated(eventMessage.getTransactionId(), userAccount, true);
                    break;
                case RESYNC:
                    userAccount = getUserAccountFromInternalId(eventMessage.getInternalId());
                    resync(eventMessage.getTransactionId(), userAccount);
                    break;
                default:
                    performResyncAllIfRequested(eventMessage);
                    break;
                }
            }
        } catch (PluginException e) {
            getPluginContext().log(LogLevel.INFO, "Failure of the subversion plugin", e);
            getPluginContext().reportOnEventHandling(eventMessage.getTransactionId(), true, eventMessage,
                    "Failure of the subversion plugin", e);
            throw e;
        }
    }

    @Override
    public void handleInProvisioningMessage(EventMessage eventMessage) throws PluginException {
    }

    @Override
    public IPluginMenuDescriptor getMenuDescriptor() {
        final String menuLabel = getPluginContext().getPluginConfigurationName();
        return new IPluginMenuDescriptor() {

            @Override
            public String getPath() {
                return getSubversionGuiUrl();
            }

            @Override
            public String getLabel() {
                return menuLabel;
            }
        };
    }

    @Override
    public Map<String, IPluginActionDescriptor> getActionDescriptors() {
        return pluginActions;
    }

    /**
     * Parse all the system accounts and synchronize with the LDAP server (if
     * required)
     * 
     * @param eventMessage
     *            an event message
     * @throws PluginException
     */
    private void performResyncAllIfRequested(EventMessage eventMessage) throws PluginException {
        if (eventMessage.getMessageType().equals(MessageType.CUSTOM) && eventMessage.getPayload() != null
                && eventMessage.getPayload() instanceof ActionMessage) {
            switch ((ActionMessage) eventMessage.getPayload()) {
            case RESYNC_ALL_ACTION:
                getPluginContext().reportMessage(eventMessage.getTransactionId(), false, "Resync started...");
                StringBuffer resyncLog = new StringBuffer();
                List<IUserAccount> userAccounts = null;
                try {
                    userAccounts = getAccountManagerPlugin().getUserAccountsFromName("*");
                } catch (AccountManagementException e) {
                    throw new PluginException("Error while looking for the user accounts", e);
                }
                if (userAccounts != null) {
                    for (IUserAccount userAccount : userAccounts) {
                        resyncLog.append("Account ").append(userAccount.getUid()).append(" checked\n");
                        try {
                            notifyAccountUpdated(eventMessage.getTransactionId(), userAccount, false);
                        } catch (PluginException e) {
                            resyncLog.append(">>Error with account ").append(userAccount.getUid())
                                    .append(", exception is : ").append(Utilities.getExceptionAsString(e))
                                    .append('\n');
                        }
                    }
                    String fileNamePrefix = getPluginContext().getPluginConfigurationName()
                            .replaceAll("[^\\p{L}\\p{Nd}]+", "").toLowerCase();
                    String resyncReportFilePath = "/" + IMafConstants.OUTPUT_FOLDER_NAME + "/" + fileNamePrefix
                            + "_resync.log";
                    OutputStreamWriter outWriter = null;
                    try {
                        OutputStream outStream = getPluginContext().writeFileInSharedStorage(resyncReportFilePath,
                                true);
                        outWriter = new OutputStreamWriter(outStream);
                        outWriter.write(resyncLog.toString());
                        outWriter.flush();
                        getPluginContext().reportMessage(eventMessage.getTransactionId(), false,
                                "Resync report written to " + resyncReportFilePath + " in the shared storage");
                    } catch (IOException e) {
                        throw new PluginException("Error while writing the resync report file", e);
                    } finally {
                        IOUtils.closeQuietly(outWriter);
                    }
                }
                getPluginContext().reportMessage(eventMessage.getTransactionId(), false, "...Resync complete");
                break;
            }
        } else {
            getPluginContext().log(LogLevel.DEBUG,
                    "Attempt to send a custom message to Subversion while it is not supported " + eventMessage);
        }
    }

    /**
     * Check if the update of the system role may have an impact on the
     * subversion LDAP configuration.
     * 
     * @param eventMessage
     *            the event message
     */
    private void checkImpactOfSystemLevelRoleUpdate(EventMessage eventMessage) {
        // A role was updated, check if this role was associated with
        // the permission SCM_DEVELOPER_PERMISSION
        // or SCM_ADMIN
        Long changedRole = eventMessage.getInternalId();
        List<String> previousListOfPermissions = ((SystemLevelRoleTypeEventMessage.PayLoad) eventMessage
                .getPayload()).getPreviousPermissionNames();

        // Check if the permission is new or removed
        List<String> currentPermissions = new ArrayList<String>();
        SystemLevelRoleType roleType = SystemLevelRoleType.getActiveRoleFromId(changedRole);
        if (roleType.systemPermissions != null) {
            for (SystemPermission systemPermission : roleType.systemPermissions) {
                currentPermissions.add(systemPermission.name);
            }
        }

        Collection<?> changedPermissions = CollectionUtils.disjunction(previousListOfPermissions,
                currentPermissions);
        if (changedPermissions.contains(IMafConstants.SCM_DEVELOPER_PERMISSION)
                || changedPermissions.contains(IMafConstants.SCM_ADMIN_PERMISSION)) {
            List<Principal> principals = Principal.getPrincipalsWithSystemLevelRoleId(changedRole);

            // Part of removed permissions
            Collection<?> removedPermissions = CollectionUtils.subtract(previousListOfPermissions,
                    currentPermissions);
            if (removedPermissions.contains(IMafConstants.SCM_DEVELOPER_PERMISSION)) {
                removeUsersFromLdapGroupFollowingRoleUpdate(principals, getDeveloperLdapGroup());
            }
            if (removedPermissions.contains(IMafConstants.SCM_ADMIN_PERMISSION)) {
                removeUsersFromLdapGroupFollowingRoleUpdate(principals, getDeliveryManagerLdapGroup());
            }

            // Part of added permission
            Collection<?> addedPermissions = CollectionUtils.subtract(currentPermissions,
                    previousListOfPermissions);
            if (addedPermissions.contains(IMafConstants.SCM_DEVELOPER_PERMISSION)) {
                addUsersToLdapGroupFollowingRoleUpdate(principals, getDeveloperLdapGroup());
            }
            if (addedPermissions.contains(IMafConstants.SCM_ADMIN_PERMISSION)) {
                addUsersToLdapGroupFollowingRoleUpdate(principals, getDeliveryManagerLdapGroup());
            }
        }
    }

    /**
     * Notify the creation of an account.
     * 
     * @param transactionId
     *            the transaction id
     * @param userAccount
     *            the user account
     */
    private void notifyAccountCreated(String transactionId, IUserAccount userAccount) throws PluginException {
        try {
            // Attempt to add the user to the group depending on its permissions
            IAuthenticationAccountWriterPlugin authWriterPlugin = getAuthWriterPlugin();
            if (userAccount.getSystemPermissionNames().contains(IMafConstants.SCM_DEVELOPER_PERMISSION)) {
                getPluginContext().reportMessage(transactionId, false,
                        "User " + userAccount.getUid() + " added to " + getDeveloperLdapGroup() + " group");
                authWriterPlugin.addUserToGroup(userAccount.getUid(), getDeveloperLdapGroup());
            }
            if (userAccount.getSystemPermissionNames().contains(IMafConstants.SCM_ADMIN_PERMISSION)) {
                getPluginContext().reportMessage(transactionId, false,
                        "User " + userAccount.getUid() + " added to " + getDeliveryManagerLdapGroup() + " group");
                authWriterPlugin.addUserToGroup(userAccount.getUid(), getDeliveryManagerLdapGroup());
            }

            getPluginContext().log(LogLevel.INFO,
                    String.format("[Transaction %s] Subversion account %s added to the LDAP groups", transactionId,
                            userAccount.getUid()));
        } catch (AccountManagementException e) {
            throw new PluginException(
                    String.format("Error while adding user %s to the LDAP groups", userAccount.getUid()), e);
        }
    }

    /**
     * Notifiy the update of an account.
     * 
     * @param transactionId
     *            the transaction id
     * @param userAccount
     *            the user account
     * @param withLogs
     *            if true log the changes performed
     */
    private void notifyAccountUpdated(String transactionId, IUserAccount userAccount, boolean withLogs)
            throws PluginException {
        try {
            // Attempt to add the user to the group depending on its permissions
            IAuthenticationAccountWriterPlugin authWriterPlugin = getAuthWriterPlugin();
            if (userAccount.getSystemPermissionNames().contains(IMafConstants.SCM_DEVELOPER_PERMISSION)) {
                if (withLogs)
                    getPluginContext().reportMessage(transactionId, false,
                            "User " + userAccount.getUid() + " added to " + getDeveloperLdapGroup() + " group");
                authWriterPlugin.addUserToGroup(userAccount.getUid(), getDeveloperLdapGroup());
            } else {
                if (withLogs)
                    getPluginContext().reportMessage(transactionId, false,
                            "User " + userAccount.getUid() + " removed from " + getDeveloperLdapGroup() + " group");
                authWriterPlugin.removeUserFromGroup(userAccount.getUid(), getDeveloperLdapGroup());
            }
            if (userAccount.getSystemPermissionNames().contains(IMafConstants.SCM_ADMIN_PERMISSION)) {
                if (withLogs)
                    getPluginContext().reportMessage(transactionId, false, "User " + userAccount.getUid()
                            + " added to " + getDeliveryManagerLdapGroup() + " group");
                authWriterPlugin.addUserToGroup(userAccount.getUid(), getDeliveryManagerLdapGroup());
            } else {
                if (withLogs)
                    getPluginContext().reportMessage(transactionId, false, "User " + userAccount.getUid()
                            + " removed from " + getDeliveryManagerLdapGroup() + " group");
                authWriterPlugin.removeUserFromGroup(userAccount.getUid(), getDeliveryManagerLdapGroup());
            }
            if (withLogs)
                getPluginContext().log(LogLevel.INFO,
                        String.format(
                                "[Transaction %s] Subversion account %s changed (added or removed from LDAP group)",
                                transactionId, userAccount.getUid()));
        } catch (AccountManagementException e) {
            throw new PluginException(String.format(
                    "Error while changing user %s (added or removed from LDAP group)", userAccount.getUid()), e);
        }
    }

    /**
     * Notify the deletion of an account.
     * 
     * @param transactionId
     *            the transaction id
     * @param previousUid
     *            the previous uid
     */
    private void notifyAccountDeleted(String transactionId, String previousUid) throws PluginException {
        try {
            // Remove the user from both groups (just in case)
            IAuthenticationAccountWriterPlugin authWriterPlugin = getAuthWriterPlugin();
            authWriterPlugin.removeUserFromGroup(previousUid, getDeveloperLdapGroup());
            getPluginContext().reportMessage(transactionId, false,
                    "User " + previousUid + " removed from " + getDeveloperLdapGroup() + " group");
            authWriterPlugin.removeUserFromGroup(previousUid, getDeliveryManagerLdapGroup());
            getPluginContext().reportMessage(transactionId, false,
                    "User " + previousUid + " removed from " + getDeliveryManagerLdapGroup() + " group");
            getPluginContext().log(LogLevel.INFO, String.format(
                    "[Transaction %s] Subversion account %s removed from LDAP groups", transactionId, previousUid));
        } catch (AccountManagementException e) {
            getPluginContext().log(LogLevel.ERROR,
                    String.format("Unable to remove the account %s from the LDAP groups", previousUid), e);
        }
    }

    /**
     * Resync.
     * 
     * @param transactionId
     *            the transaction id
     * @param userAccount
     *            the user account
     */
    private void resync(String transactionId, IUserAccount userAccount) throws PluginException {
        notifyAccountUpdated(transactionId, userAccount, true);
    }

    /**
     * Remove the specified users from the specified LDAP group.
     * 
     * @param principals
     *            the list of principals to remove
     * @param ldapGroup
     *            a LDAP group
     */
    private void removeUsersFromLdapGroupFollowingRoleUpdate(List<Principal> principals, String ldapGroup) {
        IAuthenticationAccountWriterPlugin authWriterPlugin = getAuthWriterPlugin();
        if (principals != null) {
            for (Principal principal : principals) {
                String uid = principal.uid;
                try {
                    authWriterPlugin.removeUserFromGroup(uid, ldapGroup);
                } catch (AccountManagementException e) {
                    getPluginContext().log(LogLevel.ERROR,
                            String.format("Unable to remove the user %s from the LDAP group %s", uid, ldapGroup),
                            e);
                }
            }
        }
    }

    /**
     * Add the specified users to the specified LDAP group.
     * 
     * @param principals
     *            the list of principals to add
     * @param ldapGroup
     *            a LDAP group
     */
    private void addUsersToLdapGroupFollowingRoleUpdate(List<Principal> principals, String ldapGroup) {
        IAuthenticationAccountWriterPlugin authWriterPlugin = getAuthWriterPlugin();
        if (principals != null) {
            for (Principal principal : principals) {
                String uid = principal.uid;
                try {
                    authWriterPlugin.addUserToGroup(uid, ldapGroup);
                } catch (AccountManagementException e) {
                    getPluginContext().log(LogLevel.ERROR,
                            String.format("Unable to add the user %s to the LDAP group %s", uid, ldapGroup), e);
                }
            }
        }
    }

    /**
     * Return the {@link IUserAccount} associated with the specified id.
     * 
     * @param id
     *            the user id
     * 
     * @return a user account object
     */
    private IUserAccount getUserAccountFromInternalId(Long id) throws PluginException {
        try {
            IUserAccount userAccount = getSecurityService().getUserFromId(id);
            if (userAccount == null) {
                throw new PluginException("User account not found for id = " + id);
            }
            return userAccount;
        } catch (Exception e) {
            if (e instanceof PluginException) {
                throw (PluginException) e;
            }
            throw new PluginException(e);
        }
    }

    /**
     * Get the plugin context.
     */
    private IPluginContext getPluginContext() {
        return pluginContext;
    }

    /**
     * Get the developer ldap group.
     */
    private String getDeveloperLdapGroup() {
        return developerLdapGroup;
    }

    /**
     * Get the delivery manager ldap group.
     */
    private String getDeliveryManagerLdapGroup() {
        return deliveryManagerLdapGroup;
    }

    private ISecurityService getSecurityService() {
        return securityService;
    }

    private IAuthenticationAccountWriterPlugin getAuthWriterPlugin() {
        return authWriterPlugin;
    }

    private String getSubversionGuiUrl() {
        return subversionGuiUrl;
    }

    private IAccountManagerPlugin getAccountManagerPlugin() {
        return accountManagerPlugin;
    }
}