org.ow2.proactive.scheduler.authentication.ManageUsers.java Source code

Java tutorial

Introduction

Here is the source code for org.ow2.proactive.scheduler.authentication.ManageUsers.java

Source

/*
 * ProActive Parallel Suite(TM):
 * The Open Source library for parallel and distributed
 * Workflows & Scheduling, Orchestration, Cloud Automation
 * and Big Data Analysis on Enterprise Grids & Clouds.
 *
 * Copyright (c) 2007 - 2017 ActiveEon
 * Contact: contact@activeeon.com
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation: version 3 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * If needed, contact us to obtain a release under GPL Version 2 or 3
 * or a different license than the AGPL.
 */
package org.ow2.proactive.scheduler.authentication;

import java.io.*;
import java.security.KeyException;
import java.security.PublicKey;
import java.util.*;

import org.apache.commons.cli.*;
import org.apache.commons.io.IOUtils;
import org.objectweb.proactive.utils.SecurityManagerConfigurator;
import org.ow2.proactive.authentication.FileLoginModule;
import org.ow2.proactive.authentication.crypto.CreateCredentials;
import org.ow2.proactive.authentication.crypto.Credentials;
import org.ow2.proactive.authentication.crypto.HybridEncryptionUtil;
import org.ow2.proactive.scheduler.core.properties.PASchedulerProperties;
import org.ow2.proactive.utils.Tools;

import com.google.common.base.Strings;
import com.google.common.collect.Multimap;
import com.google.common.collect.TreeMultimap;

/**
 * Manage user accounts in login.cfg and group.cfg files
 *
 * @author The ProActive Team
 * @see org.ow2.proactive.authentication.crypto.Credentials
 */
public class ManageUsers {

    private static final String newline = System.lineSeparator();

    public static final String CREATE_OPTION = "C";

    public static final String CREATE_OPTION_NAME = "create";

    public static final String UPDATE_OPTION = "U";

    public static final String UPDATE_OPTION_NAME = "update";

    public static final String DELETE_OPTION = "D";

    public static final String DELETE_OPTION_NAME = "delete";

    public static final String HELP_OPTION = "h";

    public static final String HELP_OPTION_NAME = "help";

    public static final String LOGIN_OPTION = "l";

    public static final String LOGIN_OPTION_NAME = "login";

    public static final String PWD_OPTION = "p";

    public static final String PWD_OPTION_NAME = "password";

    public static final String GROUPS_OPTION = "g";

    public static final String GROUPS_OPTION_NAME = "groups";

    public static final String KEYFILE_OPTION = "kf";

    public static final String KEYFILE_OPTION_NAME = "keyfile";

    public static final String LOGINFILE_OPTION = "lf";

    public static final String LOGINFILE_OPTION_NAME = "loginfile";

    public static final String GROUPFILE_OPTION = "gf";

    public static final String GROUPFILE_OPTION_NAME = "groupfile";

    public static final String SOURCE_LOGINFILE_OPTION = "slf";

    public static final String SOURCE_LOGINFILE_OPTION_NAME = "sourceloginfile";

    public static final String SOURCE_GROUPFILE_OPTION = "sgf";

    public static final String SOURCE_GROUPFILE_OPTION_NAME = "sourcegroupfile";

    public static final String USER_HEADER = "USER ";

    public static final String ALREADY_EXISTS_IN_LOGIN_FILE = " already exists in login file : ";

    public static final String ALREADY_EXISTS_IN_GROUP_FILE = " already exists in group file : ";

    public static final String IS_EMPTY_SKIPPING = " is empty, skipping.";

    public static final String DOES_NOT_EXIST_IN_LOGIN_FILE = " does not exist in login file : ";

    public static final String UPDATING_THIS_USER_INFORMATION = ", updating this user information.";

    public static final String DOES_NOT_EXIST_IN_GROUP_FILE = " does not exist in group file : ";

    public static final String PROVIDED_USERNAME = "Provided username ";

    /**
     * Entry point
     *
     * @param args arguments, try '-h' for help
     * @throws IOException
     * @throws ParseException
     * @see org.ow2.proactive.authentication.crypto.Credentials
     */
    public static void main(String[] args) {

        try {

            manageUsers(args);

        } catch (ManageUsersException e) {
            System.err.println("ERROR : " + e.getMessage());
            if (e.getCause() != null) {
                System.err.println(e.getCause().getMessage());
            }
            if (e.getAdditionalInfo() != null) {
                System.out.println(e.getAdditionalInfo());
            }
            System.exit(1);
        }

        System.exit(0);
    }

    public static void manageUsers(String... args) throws ManageUsersException {
        SecurityManagerConfigurator.configureSecurityManager(
                CreateCredentials.class.getResource("/all-permissions.security.policy").toString());

        Console console = System.console();
        /**
         * default values
         */
        String pubKeyPath = null;
        PublicKey pubKey = null;
        UserInfo userInfo = new UserInfo();
        String loginFilePath = getLoginFilePath();
        String groupFilePath = getGroupFilePath();
        String sourceLoginFilePath = null;
        String sourceGroupFilePath = null;
        Action action = null;

        Options options = new Options();

        CommandLine cmd = getCommandLine(args, loginFilePath, groupFilePath, options);

        if (cmd.hasOption(HELP_OPTION_NAME) || cmd.getOptions().length == 0) {
            displayHelp(options);
            return;
        }

        action = Action.getAction(cmd);

        if (cmd.hasOption(LOGIN_OPTION_NAME)) {
            userInfo.setLogin(cmd.getOptionValue(LOGIN_OPTION_NAME));
        }
        if (cmd.hasOption(PWD_OPTION_NAME)) {
            userInfo.setPassword(cmd.getOptionValue(PWD_OPTION_NAME));
        }
        if (cmd.hasOption(GROUPS_OPTION_NAME)) {
            String groupString = cmd.getOptionValue(GROUPS_OPTION_NAME);
            userInfo.setGroups(Arrays.asList(groupString.split(",")));
        }

        if (cmd.hasOption(LOGINFILE_OPTION_NAME)) {
            loginFilePath = cmd.getOptionValue(LOGINFILE_OPTION_NAME);
        }
        if (cmd.hasOption(GROUPFILE_OPTION_NAME)) {
            groupFilePath = cmd.getOptionValue(GROUPFILE_OPTION_NAME);
        }
        if (cmd.hasOption(KEYFILE_OPTION_NAME)) {
            pubKeyPath = cmd.getOptionValue(KEYFILE_OPTION_NAME);
        }

        if (cmd.hasOption(SOURCE_LOGINFILE_OPTION_NAME)) {
            if (action == Action.DELETE) {
                exitWithErrorMessage("Cannot use action delete with source login file.", null, null);
            }
            if (!cmd.hasOption(SOURCE_GROUPFILE_OPTION_NAME) && action == Action.CREATE) {
                exitWithErrorMessage(
                        "Source group file must be provided when creating users with source login file.", null,
                        null);
            }
            sourceLoginFilePath = cmd.getOptionValue(SOURCE_LOGINFILE_OPTION_NAME);
            userInfo = null;
        }
        if (cmd.hasOption(SOURCE_GROUPFILE_OPTION_NAME)) {
            if (action == Action.DELETE) {
                exitWithErrorMessage("Cannot use action delete with source group file.", null, null);
            }
            if (!cmd.hasOption(SOURCE_LOGINFILE_OPTION_NAME) && action == Action.CREATE) {
                exitWithErrorMessage(
                        "Source login file must be provided when creating users with source group file.", null,
                        null);
            }
            sourceGroupFilePath = cmd.getOptionValue(SOURCE_GROUPFILE_OPTION_NAME);
            userInfo = null;
        }

        if (pubKeyPath == null) {
            pubKeyPath = getPublicKeyFilePath();
        }

        try {
            pubKey = Credentials.getPublicKey(pubKeyPath);
        } catch (KeyException e) {
            exitWithErrorMessage("Could not retrieve public key from '" + pubKeyPath, null, e);
        }
        boolean nonInteractive = checkInteractivity(userInfo, sourceLoginFilePath, sourceGroupFilePath, action);

        if (!nonInteractive) {
            askInteractively(console, userInfo, action);
        }

        updateAccounts(pubKey, userInfo, loginFilePath, groupFilePath, action, sourceLoginFilePath,
                sourceGroupFilePath);
    }

    private static boolean checkInteractivity(UserInfo userInfo, String sourceLoginFilePath,
            String sourceGroupFilePath, Action action) {
        boolean nonInteractive = true;
        if (sourceLoginFilePath != null || sourceGroupFilePath != null) {
            nonInteractive = true;
        } else {
            boolean l = userInfo.isLoginSet();
            boolean p = userInfo.isPasswordSet();
            boolean g = userInfo.isGroupSet();
            switch (action) {
            case CREATE:
                nonInteractive = l && p && g;
                break;
            case UPDATE:
                nonInteractive = (l && p) || (l && g);
                break;
            case DELETE:
                nonInteractive = l;
                break;
            }
        }
        return nonInteractive;
    }

    /**
     * Ask the user for parameters missing on the command line
     */
    private static void askInteractively(Console console, UserInfo userInfo, Action action) {
        System.out.println(action);
        System.out.print("login: ");
        if (!userInfo.isLoginSet()) {
            userInfo.setLogin(console.readLine());
        } else {
            System.out.println(userInfo.getLogin());
        }
        System.out.print("password: ");
        if ((action.isCreate() && !userInfo.isPasswordSet())
                || (action.isUpdate() && !userInfo.isPasswordSet()) && !userInfo.isGroupSet()) {
            userInfo.setPassword(new String(console.readPassword()));
        } else {
            System.out.println("*******");
        }

        if (action.isCreate() && !userInfo.isGroupSet()) {
            System.out.print("groups for user " + userInfo.getLogin() + ":");
            String groupString = console.readLine();
            userInfo.setGroups(Arrays.asList(groupString.split(",")));
        }
    }

    /**
     * Update the accounts in the login and config files
     *
     * @throws ManageUsersException
     */
    private static void updateAccounts(final PublicKey pubKey, final UserInfo userInfo, final String loginFilePath,
            final String groupFilePath, final Action action, final String sourceLoginFile,
            final String sourceGroupFile) throws ManageUsersException {
        try {
            Properties destinationLoginProps = new Properties();
            try (InputStreamReader stream = new InputStreamReader(new FileInputStream(loginFilePath))) {
                destinationLoginProps.load(stream);
            } catch (Exception e) {
                exitWithErrorMessage("could not read login file : " + loginFilePath, null, e);
            }

            Multimap<String, String> destinationGroupsMap = loadGroups(groupFilePath);

            Properties sourceLoginProps = new Properties();
            if (sourceLoginFile != null) {
                try (InputStreamReader stream = new InputStreamReader(new FileInputStream(sourceLoginFile))) {
                    sourceLoginProps.load(stream);
                } catch (Exception e) {
                    exitWithErrorMessage("could not read source login file : " + sourceLoginFile, null, e);
                }
            } else if (userInfo != null) {
                if (userInfo.getPassword() == null) {
                    // password can be null in case of account deletion
                    sourceLoginProps.put(userInfo.getLogin(), "");
                } else {
                    sourceLoginProps.put(userInfo.getLogin(), userInfo.getPassword());
                }
            }

            Multimap<String, String> sourceGroupsMap = null;

            if (sourceGroupFile != null) {
                sourceGroupsMap = loadGroups(sourceGroupFile);
            } else {
                sourceGroupsMap = TreeMultimap.create();
                if (userInfo != null && !userInfo.getGroups().isEmpty()) {
                    for (String group : userInfo.getGroups()) {
                        sourceGroupsMap.put(userInfo.getLogin(), group);
                    }
                }
            }
            Collection<String> sourceLoginNames = sourceLoginProps.stringPropertyNames();
            if (sourceLoginNames.isEmpty()) {
                sourceLoginNames = sourceGroupsMap.keySet();
            }

            boolean bulkMode = sourceLoginNames.size() > 1;

            for (String user : sourceLoginNames) {
                UserInfo sourceUserInfo = new UserInfo();
                sourceUserInfo.setLogin(user);
                sourceUserInfo.setPassword((String) sourceLoginProps.get(user));
                if (sourceGroupsMap.containsKey(user)) {
                    sourceUserInfo.setGroups(sourceGroupsMap.get(user));
                }

                switch (action) {
                case CREATE:
                    createAccount(pubKey, sourceUserInfo, loginFilePath, groupFilePath, destinationLoginProps,
                            destinationGroupsMap);
                    break;
                case UPDATE:
                    updateAccount(pubKey, sourceUserInfo, loginFilePath, destinationLoginProps,
                            destinationGroupsMap, bulkMode);
                    break;
                case DELETE:
                    deleteAccount(sourceUserInfo, loginFilePath, groupFilePath, destinationLoginProps,
                            destinationGroupsMap);
                    break;
                }
            }

            storeLoginFile(loginFilePath, destinationLoginProps);

            storeGroups(groupFilePath, destinationGroupsMap);

        } catch (Throwable t) {
            exitWithErrorMessage("Unexpected error", null, t);
        }
    }

    private static void deleteAccount(UserInfo userInfo, String loginFilePath, String groupFilePath,
            Properties props, Multimap<String, String> groupsMap) throws ManageUsersException {
        if (!userInfo.isLoginSet()) {
            warnWithMessage(PROVIDED_USERNAME + IS_EMPTY_SKIPPING);
            return;
        }
        if (!props.containsKey(userInfo.getLogin())) {
            warnWithMessage(USER_HEADER + userInfo.getLogin() + DOES_NOT_EXIST_IN_LOGIN_FILE + loginFilePath);
        }
        if (!groupsMap.containsKey(userInfo.getLogin())) {
            warnWithMessage(USER_HEADER + userInfo.getLogin() + DOES_NOT_EXIST_IN_GROUP_FILE + groupFilePath);
        }
        props.remove(userInfo.getLogin());
        groupsMap.removeAll(userInfo.getLogin());
        System.out.println("Deleted user " + userInfo.getLogin());
    }

    private static void updateAccount(PublicKey pubKey, UserInfo userInfo, String loginFilePath, Properties props,
            Multimap<String, String> groupsMap, boolean bulkMode) throws ManageUsersException, KeyException {
        if (!userInfo.isLoginSet()) {
            warnWithMessage(PROVIDED_USERNAME + IS_EMPTY_SKIPPING);
            return;
        }
        if (!props.containsKey(userInfo.getLogin())) {
            String userDoesNotExistInLoginFileMessage = USER_HEADER + userInfo.getLogin()
                    + DOES_NOT_EXIST_IN_LOGIN_FILE + loginFilePath;
            if (userInfo.isPasswordSet() && userInfo.isGroupSet()) {
                warnWithMessage(userDoesNotExistInLoginFileMessage + ", create this user.");
            } else {
                if (bulkMode) {
                    warnWithMessage(userDoesNotExistInLoginFileMessage
                            + " and not enough information were provided to create a new user, skipping.");
                    return;
                } else {
                    exitWithErrorMessage(
                            userDoesNotExistInLoginFileMessage
                                    + " and not enough information were provided to create a new user.",
                            null, null);
                }
            }
        }
        if (userInfo.isPasswordSet()) {
            updateUserPassword(pubKey, userInfo.getLogin(), userInfo.getPassword(), props);
            System.out.println("Changed password for user " + userInfo.getLogin());
        }
        if (!userInfo.getGroups().isEmpty()) {
            updateUserGroups(userInfo.getLogin(), userInfo.getGroups(), groupsMap);
        }

        System.out.println("Updated user " + userInfo.getLogin());
    }

    private static void createAccount(PublicKey pubKey, UserInfo userInfo, String loginFilePath,
            String groupFilePath, Properties props, Multimap<String, String> groupsMap)
            throws ManageUsersException, KeyException {
        if (!userInfo.isLoginSet()) {
            warnWithMessage(PROVIDED_USERNAME + IS_EMPTY_SKIPPING);
            return;
        }
        if (!userInfo.isPasswordSet()) {
            warnWithMessage("Provided password for user " + userInfo.getLogin() + IS_EMPTY_SKIPPING);
            return;
        }
        if (!userInfo.isGroupSet()) {
            warnWithMessage("Provided groups for user " + userInfo.getLogin() + IS_EMPTY_SKIPPING);
            return;
        }
        if (props.containsKey(userInfo.getLogin())) {
            warnWithMessage(USER_HEADER + userInfo.getLogin() + ALREADY_EXISTS_IN_LOGIN_FILE + loginFilePath
                    + UPDATING_THIS_USER_INFORMATION);
        }
        if (groupsMap.containsKey(userInfo.getLogin())) {
            warnWithMessage(USER_HEADER + userInfo.getLogin() + ALREADY_EXISTS_IN_GROUP_FILE + groupFilePath
                    + UPDATING_THIS_USER_INFORMATION);
        }
        updateUserPassword(pubKey, userInfo.getLogin(), userInfo.getPassword(), props);
        updateUserGroups(userInfo.getLogin(), userInfo.getGroups(), groupsMap);
        System.out.println("Created user " + userInfo.getLogin());

    }

    /**
     * Build the command line options and parse
     */
    private static CommandLine getCommandLine(String[] args, String loginFilePath, String groupFilePath,
            Options options) throws ManageUsersException {
        Option opt = new Option(HELP_OPTION, HELP_OPTION_NAME, false, "Display this help");
        opt.setRequired(false);
        options.addOption(opt);
        OptionGroup optionGroup = new OptionGroup();
        optionGroup.setRequired(false);

        opt = new Option(CREATE_OPTION, CREATE_OPTION_NAME, true, "Action to create a user");
        opt.setArgName(CREATE_OPTION_NAME.toUpperCase());
        opt.setArgs(0);
        opt.setRequired(false);
        optionGroup.addOption(opt);

        opt = new Option(UPDATE_OPTION, UPDATE_OPTION_NAME, true,
                "Action to update an existing user. Updating a user means to change the user's password or group membership.");
        opt.setArgName(UPDATE_OPTION_NAME.toUpperCase());
        opt.setArgs(0);
        opt.setRequired(false);
        optionGroup.addOption(opt);

        opt = new Option(DELETE_OPTION, DELETE_OPTION_NAME, true, "Action to delete an existing user");
        opt.setArgName(DELETE_OPTION_NAME.toUpperCase());
        opt.setArgs(0);
        opt.setRequired(false);
        optionGroup.addOption(opt);
        options.addOptionGroup(optionGroup);

        opt = new Option(LOGIN_OPTION, LOGIN_OPTION_NAME, true,
                "Generate credentials for this specific user, will be asked interactively if not specified");
        opt.setArgName(LOGIN_OPTION_NAME.toUpperCase());
        opt.setArgs(1);
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option(PWD_OPTION, PWD_OPTION_NAME, true,
                "Password of the user, if the user is created or updated, will be asked interactively if not specified");
        opt.setArgName(PWD_OPTION_NAME.toUpperCase());
        opt.setArgs(1);
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option(GROUPS_OPTION, GROUPS_OPTION_NAME, true,
                "A comma-separated list of groups the user must be member of. Can be used when the user is created or updated");
        opt.setArgName(GROUPS_OPTION_NAME.toUpperCase());
        opt.setArgs(1);
        opt.setRequired(false);
        options.addOption(opt);

        optionGroup.setRequired(false);
        opt = new Option(KEYFILE_OPTION, KEYFILE_OPTION_NAME, true,
                "Public key path on the local filesystem [default:" + getPublicKeyFilePath() + "]");
        opt.setArgName(KEYFILE_OPTION_NAME.toUpperCase());
        opt.setArgs(1);
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option(LOGINFILE_OPTION, LOGINFILE_OPTION_NAME, true,
                "Path to the login file in use [default:" + loginFilePath + "]");
        opt.setArgName(LOGINFILE_OPTION_NAME.toUpperCase());
        opt.setArgs(1);
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option(GROUPFILE_OPTION, GROUPFILE_OPTION_NAME, true,
                "Path to the group file in use [default:" + groupFilePath + "]");
        opt.setArgName(GROUPFILE_OPTION_NAME.toUpperCase());
        opt.setArgs(1);
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option(SOURCE_LOGINFILE_OPTION, SOURCE_LOGINFILE_OPTION_NAME, true,
                "Path to a source login file, used for bulk creation or bulk update. The source login file must contain clear text passwords in the format username:password");
        opt.setArgName(SOURCE_LOGINFILE_OPTION_NAME.toUpperCase());
        opt.setArgs(1);
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option(SOURCE_GROUPFILE_OPTION, SOURCE_GROUPFILE_OPTION_NAME, true,
                "Path to a source group file, used for bulk creation or bulk update. The source group file must contain group assignements in the format username:group");
        opt.setArgName(SOURCE_GROUPFILE_OPTION_NAME.toUpperCase());
        opt.setArgs(1);
        opt.setRequired(false);
        options.addOption(opt);

        CommandLineParser parser = new DefaultParser();
        CommandLine cmd = null;
        try {
            cmd = parser.parse(options, args);
        } catch (Exception e) {
            exitWithErrorMessage(newline + e.getMessage() + newline, "type -h or --help to display help screen",
                    null);
        }
        return cmd;
    }

    private static void updateUserGroups(String login, Collection<String> groups,
            Multimap<String, String> groupsMap) {
        if (!groups.isEmpty()) {
            groupsMap.removeAll(login);
            for (String group : groups) {
                if (!group.isEmpty()) {
                    groupsMap.put(login, group);
                    System.out.println("Adding group " + group + " to user " + login);
                }
            }
        }
    }

    private static void updateUserPassword(PublicKey pubKey, String login, String password, Properties props)
            throws KeyException {
        String encodedPassword;
        encodedPassword = HybridEncryptionUtil.encryptStringToBase64(password, pubKey,
                FileLoginModule.ENCRYPTED_DATA_SEP);
        props.put(login, encodedPassword);

    }

    private static Multimap<String, String> loadGroups(String groupFilePath) throws ManageUsersException {
        Multimap<String, String> groupsMap = TreeMultimap.create();
        String line = null;

        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(groupFilePath)))) {
            while ((line = reader.readLine()) != null) {
                if (!line.trim().isEmpty()) {
                    String[] u2g = line.split(":");
                    if (u2g.length == 2) {
                        groupsMap.put(u2g[0].trim(), u2g[1].trim());
                    }
                }
            }
        } catch (IOException e) {
            exitWithErrorMessage("could not read group file : " + groupFilePath, null, e);
        }
        return groupsMap;
    }

    /**
     * Stores the logins into login.cfg
     */
    private static void storeLoginFile(String loginFilePath, Properties props) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(loginFilePath)))) {
            props.store(writer, null);
        }
        List<String> lines = null;

        try (FileInputStream stream = new FileInputStream(loginFilePath)) {
            lines = IOUtils.readLines(stream);
        }

        TreeMap<String, String> sortedUsers = new TreeMap<>();
        for (String line : lines) {
            if (!(line.isEmpty() || line.startsWith("#"))) {
                String[] loginAndPwd = line.split("=", 2);
                sortedUsers.put(loginAndPwd[0], loginAndPwd[1]);
            }
        }
        List<String> modifiedLines = new ArrayList<>(sortedUsers.size());
        for (Map.Entry entry : sortedUsers.entrySet()) {
            modifiedLines.add(entry.getKey() + ":" + entry.getValue());
        }
        try (BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(loginFilePath)))) {
            IOUtils.writeLines(modifiedLines, System.getProperty("line.separator"), writer);
        }
        System.out.println("Stored login file in " + loginFilePath);
    }

    /**
     * Stores the groups in group.cfg
     */
    private static void storeGroups(String groupFilePath, Multimap<String, String> groups)
            throws ManageUsersException {
        try {
            try (PrintWriter writer = new PrintWriter(
                    new OutputStreamWriter(new FileOutputStream(groupFilePath)))) {
                for (Map.Entry<String, String> userEntry : groups.entries()) {
                    writer.println(userEntry.getKey() + ":" + userEntry.getValue());
                }
            }

        } catch (IOException e) {
            exitWithErrorMessage("could not write group file : " + groupFilePath, null, e);
        }
        System.out.println("Stored group file in " + groupFilePath);
    }

    private static void exitWithErrorMessage(String errorMessage, String infoMessage, Throwable e)
            throws ManageUsersException {
        throw new ManageUsersException(errorMessage, e, infoMessage);
    }

    private static void warnWithMessage(String warnMessage) {
        System.err.println("WARN : " + warnMessage);
    }

    private static String getLoginFilePath() {
        return getSchedulerFile(PASchedulerProperties.SCHEDULER_LOGIN_FILENAME.getValueAsString());
    }

    private static String getGroupFilePath() {
        return getSchedulerFile(PASchedulerProperties.SCHEDULER_GROUP_FILENAME.getValueAsString());
    }

    private static String getPublicKeyFilePath() {
        return getSchedulerFile(PASchedulerProperties.SCHEDULER_AUTH_PUBKEY_PATH.getValueAsString());
    }

    private static String getSchedulerFile(String path) {
        String schedulerFile = path;
        if (!(new File(schedulerFile).isAbsolute())) {
            //file path is relative, so we complete the path with the prefix Scheduler_Home constant
            schedulerFile = PASchedulerProperties.SCHEDULER_HOME.getValueAsString() + File.separator + path;
        }
        return schedulerFile;
    }

    private static void displayHelp(Options options) {
        HelpFormatter hf = new HelpFormatter();
        hf.setWidth(135);
        hf.printHelp("proactive-users" + Tools.shellExtension(), "", options, "", true);
    }

    enum Action {
        CREATE("Creating User."), UPDATE("Updating existing user"), DELETE("Deleting user");
        final String message;

        Action(String message) {
            this.message = message;
        }

        public static Action getAction(CommandLine cmd) {
            if (cmd.hasOption(CREATE_OPTION_NAME)) {
                return Action.CREATE;
            } else if (cmd.hasOption(UPDATE_OPTION_NAME)) {
                return Action.UPDATE;
            } else if (cmd.hasOption(DELETE_OPTION_NAME)) {
                return Action.DELETE;
            } else {
                throw new IllegalArgumentException(
                        "Command line does not contain, create, update or delete action: " + cmd);
            }
        }

        public boolean isCreate() {
            return this == Action.CREATE;
        }

        public boolean isUpdate() {
            return this == Action.UPDATE;
        }

        public boolean isDelete() {
            return this == Action.DELETE;
        }

        @Override
        public String toString() {
            return message;
        }
    }

    static class UserInfo {
        private String login;

        private String password;

        private Collection<String> groups = Collections.emptyList();

        public UserInfo() {
        }

        public boolean isLoginSet() {
            return !Strings.isNullOrEmpty(login);
        }

        public boolean isPasswordSet() {
            return !Strings.isNullOrEmpty(password);
        }

        public boolean isGroupSet() {
            return groups != null && !groups.isEmpty();
        }

        public String getLogin() {
            return login;
        }

        public void setLogin(String login) {
            this.login = login;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public Collection<String> getGroups() {
            return groups;
        }

        public void setGroups(Collection<String> groups) {
            this.groups = groups;
        }
    }

    static class ManageUsersException extends Exception {
        public String getAdditionalInfo() {
            return additionalInfo;
        }

        private String additionalInfo = null;

        public ManageUsersException(String message) {
            super(message);
        }

        public ManageUsersException(String message, Throwable cause) {
            super(message, cause);
        }

        public ManageUsersException(String message, Throwable cause, String additionalInfo) {
            super(message, cause);
            this.additionalInfo = additionalInfo;
        }
    }

}