changeset 1755:5abd45b55267

Refactor thermostat setup model. Changeset changed from HEAD in the following ways: - No use of FileStorageCredentials (see PR2583 for details) - Adapting MongodbUserSetup{,Test} so as to write/test correct web.auth credentials format. - Use libs/create-user.js rather than lib/create-user.js Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2015-August/015518.html PR2581
author Severin Gehwolf <sgehwolf@redhat.com>
date Wed, 02 Sep 2015 15:27:53 +0200
parents 431ee7d4caed
children a385e862f77f
files setup-command/command/pom.xml setup-command/command/src/main/java/com/redhat/thermostat/setup/command/SetupCommand.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/CredentialFinder.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/MongoUserSetupView.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/MongodbUserSetupException.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/SetupWindow.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/ThermostatSetup.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/ThermostatSetupImpl.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/UserRoles.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/CredentialFinder.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/CredentialsFileCreator.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/MongodbUserSetup.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/MongodbUserSetupException.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/PersistableSetup.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/PropertiesWriter.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/RoleSetup.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/StampFiles.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/StructureInformation.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/ThermostatSetup.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/ThermostatUserSetup.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/UserCredsValidator.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/UserPropertiesFinder.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/UserRoles.java setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/UserSetup.java setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/CredentialFinderTest.java setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/ThermostatSetupImplTest.java setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/CredentialFinderTest.java setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/CredentialsFileCreatorTest.java setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/MongodbUserSetupTest.java setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/PropertiesWriterTest.java setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/StampFilesTest.java setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/StructureInformationTest.java setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/TestRootHelper.java setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/ThermostatSetupTest.java setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/ThermostatUserSetupTest.java setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/UserCredsValidatorTest.java setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/UserPropertiesFinderTest.java
diffstat 37 files changed, 3253 insertions(+), 1512 deletions(-) [+]
line wrap: on
line diff
--- a/setup-command/command/pom.xml	Fri Aug 21 15:52:23 2015 -0400
+++ b/setup-command/command/pom.xml	Wed Sep 02 15:27:53 2015 +0200
@@ -63,6 +63,7 @@
             <Private-Package>
               com.redhat.thermostat.setup.command,
               com.redhat.thermostat.setup.command.internal,
+              com.redhat.thermostat.setup.command.internal.model,
               com.redhat.thermostat.setup.command.locale,
             </Private-Package>
             <Bundle-Activator>com.redhat.thermostat.setup.command.internal.Activator</Bundle-Activator>
--- a/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/SetupCommand.java	Fri Aug 21 15:52:23 2015 -0400
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/SetupCommand.java	Wed Sep 02 15:27:53 2015 +0200
@@ -47,8 +47,7 @@
 import com.redhat.thermostat.internal.utils.laf.ThemeManager;
 import com.redhat.thermostat.launcher.Launcher;
 import com.redhat.thermostat.setup.command.internal.SetupWindow;
-import com.redhat.thermostat.setup.command.internal.ThermostatSetup;
-import com.redhat.thermostat.setup.command.internal.ThermostatSetupImpl;
+import com.redhat.thermostat.setup.command.internal.model.ThermostatSetup;
 import com.redhat.thermostat.shared.config.CommonPaths;
 import com.redhat.thermostat.shared.locale.LocalizedString;
 
@@ -98,7 +97,7 @@
 
     //package-private for testing
     void createMainWindowAndRun() throws CommandException {
-        thermostatSetup = new ThermostatSetupImpl(launcher, paths, console);
+        thermostatSetup = ThermostatSetup.create(launcher, paths, console);
         mainWindow = new SetupWindow(thermostatSetup);
         mainWindow.run();
     }
--- a/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/CredentialFinder.java	Fri Aug 21 15:52:23 2015 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-/*
- * Copyright 2012-2015 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.setup.command.internal;
-
-import com.redhat.thermostat.shared.config.CommonPaths;
-
-import java.io.File;
-import java.io.IOException;
-
-public class CredentialFinder {
-
-    private final CommonPaths paths;
-
-    public CredentialFinder(CommonPaths paths) {
-        this.paths = paths;
-    }
-
-    public File getConfiguration(String name) throws IOException {
-        File systemFile = getConfigurationFile(paths.getSystemConfigurationDirectory(), name);
-        if (isUsable(systemFile)) {
-            return systemFile;
-        }
-        File userFile = getConfigurationFile(paths.getUserConfigurationDirectory(), name);
-        if (isUsable(userFile)) {
-            return userFile;
-        }
-        return null;
-    }
-
-    //package-private for testing
-    File getConfigurationFile(File directory, String name) {
-        return new File(directory, name);
-    }
-
-    //package-private for testing
-    boolean isUsable(File file) throws IOException {
-        if (file.exists()) {
-            return file.isFile() && file.canRead() && file.canWrite();
-        } else {
-            //Check that the parent directory is not read-only,
-            //so file can be created
-            File canonicalFile = file.getCanonicalFile();
-            return canonicalFile.getParentFile().canWrite();
-        }
-    }
-}
--- a/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/MongoUserSetupView.java	Fri Aug 21 15:52:23 2015 -0400
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/MongoUserSetupView.java	Wed Sep 02 15:27:53 2015 +0200
@@ -307,8 +307,8 @@
         return passwordField1.getPassword();
     }
 
-    public void setPassword(String password) {
-        this.passwordField1.setText(password);
-        this.passwordField2.setText(password);
+    public void setPassword(char[] password) {
+        this.passwordField1.setText(String.valueOf(password));
+        this.passwordField2.setText(String.valueOf(password));
     }
 }
--- a/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/MongodbUserSetupException.java	Fri Aug 21 15:52:23 2015 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/*
- * Copyright 2012-2015 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.setup.command.internal;
-
-public class MongodbUserSetupException extends Exception {
-
-    public MongodbUserSetupException(String message) {
-        super(message);
-    }
-
-    public MongodbUserSetupException(Throwable cause) {
-        super(cause);
-    }
-
-    public MongodbUserSetupException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}
--- a/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/SetupWindow.java	Fri Aug 21 15:52:23 2015 -0400
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/SetupWindow.java	Wed Sep 02 15:27:53 2015 +0200
@@ -55,6 +55,9 @@
 import javax.swing.SwingWorker;
 
 import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.setup.command.internal.model.MongodbUserSetupException;
+import com.redhat.thermostat.setup.command.internal.model.ThermostatSetup;
+import com.redhat.thermostat.setup.command.internal.model.UserRoles;
 import com.redhat.thermostat.setup.command.locale.LocaleResources;
 import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.shared.locale.Translate;
@@ -77,9 +80,9 @@
 
     private static final String DEFAULT_AGENT_USER = "agent-tester";
     private static final String DEFAULT_CLIENT_USER = "client-tester";
-    private static final String DEFAULT_USER_PASSWORD = "tester";
+    private static final char[] DEFAULT_USER_PASSWORD = new char[] { 't', 'e', 's', 't', 'e', 'r' };
     private static final String DEFAULT_STORAGE_USER = "mongodevuser";
-    private static final String DEFAULT_STORAGE_PASSWORD = "mongodevpassword";
+    private static final char[] DEFAULT_STORAGE_PASSWORD = new char[] { 'm', 'o', 'n', 'g', 'o', 'd', 'e', 'v', 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' };
     private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
 
     public SetupWindow(ThermostatSetup thermostatSetup) {
@@ -244,23 +247,19 @@
             public Void doInBackground() {
                 mongoUserSetupView.disableButtons();
                 userPropertiesView.disableButtons();
-
+                thermostatSetup.createMongodbUser(storageUsername, storagePassword);
                 try {
-                    runMongoSetup();
-                } catch (MongodbUserSetupException e) {
-                    setupFailed = true;
-                    return null;
+                    if (userPropertiesView.makeAgentUserSelected()) {
+                        thermostatSetup.createAgentUser(DEFAULT_AGENT_USER, DEFAULT_USER_PASSWORD);
+                    }
+                    if (userPropertiesView.makeClientAdminSelected()) {
+                        thermostatSetup.createClientAdminUser(DEFAULT_CLIENT_USER, DEFAULT_USER_PASSWORD);
+                    }
+                    thermostatSetup.commit();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    shutdown();
                 }
-
-                if (thermostatSetup.isWebAppInstalled()) {
-                    try {
-                        runPropertiesSetup();
-                    } catch (IOException e) {
-                        setupFailed = true;
-                        return null;
-                    }
-                }
-
                 return null;
             }
 
@@ -274,47 +273,6 @@
         worker.execute();
     }
 
-    private void runMongoSetup() throws MongodbUserSetupException {
-        thermostatSetup.createMongodbUser(storageUsername, storagePassword);
-    }
-
-    private void runPropertiesSetup() throws IOException {
-        String[] agentRoles = null;
-        String[] clientRoles = null;
-        if (userPropertiesView.makeAgentUserSelected()) {
-            agentRoles = new String[] {
-                    UserRoles.CMD_CHANNEL_VERIFY,
-                    UserRoles.LOGIN,
-                    UserRoles.PREPARE_STATEMENT,
-                    UserRoles.PURGE,
-                    UserRoles.REGISTER_CATEGORY,
-                    UserRoles.ACCESS_REALM,
-                    UserRoles.SAVE_FILE,
-                    UserRoles.WRITE,
-                    UserRoles.GRANT_FILES_WRITE_ALL,
-            };
-        }
-        if (userPropertiesView.makeClientAdminSelected()) {
-            clientRoles = new String[] {
-                    UserRoles.GRANT_AGENTS_READ_ALL,
-                    UserRoles.CMD_CHANNEL_GENERATE,
-                    UserRoles.GRANT_HOSTS_READ_ALL,
-                    UserRoles.LOAD_FILE,
-                    UserRoles.LOGIN,
-                    UserRoles.PREPARE_STATEMENT,
-                    UserRoles.READ,
-                    UserRoles.ACCESS_REALM,
-                    UserRoles.REGISTER_CATEGORY,
-                    UserRoles.GRANT_VMS_READ_BY_USERNAME_ALL,
-                    UserRoles.GRANT_VMS_READ_BY_VM_ID_ALL,
-                    UserRoles.GRANT_FILES_READ_ALL,
-                    UserRoles.WRITE,
-            };
-        }
-        thermostatSetup.createThermostatUser(DEFAULT_AGENT_USER, DEFAULT_USER_PASSWORD.toCharArray(), agentRoles);
-        thermostatSetup.createThermostatUser(DEFAULT_CLIENT_USER, DEFAULT_USER_PASSWORD.toCharArray(), clientRoles);
-    }
-
     private void showView(SetupView view) {
         mainView.add(view.getUiComponent(), BorderLayout.CENTER);
         view.setTitleAndProgress(title, progress);
--- a/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/ThermostatSetup.java	Fri Aug 21 15:52:23 2015 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-/*
- * Copyright 2012-2015 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.setup.command.internal;
-
-import java.io.IOException;
-
-public interface ThermostatSetup {
-
-    /**
-     * Creates a Mongodb User and a web.auth
-     * file (if webapp is installed) for the
-     * provided username and password
-     *
-     * @param username
-     * @param password
-     * @throws MongodbUserSetupException
-     */
-    void createMongodbUser(String username, char[] password) throws MongodbUserSetupException;
-
-    /**
-     * Creates entries in the thermostat-users.properties
-     * and thermostat-roles.properties for the provided
-     * username, password and roles
-     *
-     * @param username
-     * @param password
-     * @param roles
-     * @throws IOException
-     */
-    void createThermostatUser(String username, char[] password, String[] roles) throws IOException;
-
-    /**
-     * Checks if webapp is installed on the system
-     *
-     * @return true if webapp is installed, false otherwise
-     */
-    boolean isWebAppInstalled();
-}
--- a/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/ThermostatSetupImpl.java	Fri Aug 21 15:52:23 2015 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,426 +0,0 @@
-/*
- * Copyright 2012-2015 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.setup.command.internal;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.PosixFilePermissions;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Properties;
-import java.util.Set;
-
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
-import com.redhat.thermostat.common.cli.Console;
-import com.redhat.thermostat.common.tools.ApplicationState;
-import com.redhat.thermostat.launcher.Launcher;
-import com.redhat.thermostat.setup.command.locale.LocaleResources;
-import com.redhat.thermostat.shared.config.CommonPaths;
-import com.redhat.thermostat.shared.locale.Translate;
-
-
-public class ThermostatSetupImpl implements ThermostatSetup {
-    private static final String WEB_AUTH_FILE = "web.auth";
-    private static final String USERS_PROPERTIES = "thermostat-users.properties";
-    private static final String ROLES_PROPERTIES = "thermostat-roles.properties";
-    private static final String MONGO_INPUT_SCRIPT = "/tmp/mongo-input.js";
-    private static final String THERMOSTAT_AGENT = "thermostat-agent";
-    private static final String THERMOSTAT_CLIENT = "thermostat-client";
-    private static final String THERMOSTAT_CMDC = "thermostat-cmdc";
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-    private static final String[] STORAGE_START_ARGS = {"storage", "--start", "--permitLocalhostException"};
-    private static final String[] STORAGE_STOP_ARGS = {"storage", "--stop"};
-    private static final Set<PosixFilePermission> credFilePermissions = EnumSet.of(
-            PosixFilePermission.OWNER_READ,
-            PosixFilePermission.OWNER_WRITE
-    );
-
-    private static boolean storageFailed = false;
-    private List<ActionListener<ApplicationState>> listeners;
-    private String setupTmpUnlockContent;
-    private String webApp;
-    private String setupUnlockContentRegular;
-    private String userDoneFile;
-    private String createUserScript;
-    private CredentialFinder finder;
-    private File setupCompleteFile;
-    private File agentAuthFile;
-    private Launcher launcher;
-    private Properties roleProps;
-
-    public ThermostatSetupImpl(Launcher launcher, CommonPaths paths, Console console) {
-        this(launcher, paths, console, new CredentialFinder(paths));
-    }
-
-    //package-private constructor for testing
-    ThermostatSetupImpl(Launcher launcher, CommonPaths paths, Console console, CredentialFinder finder) {
-        this.launcher = launcher;
-        this.finder = finder;
-        listeners = new ArrayList<>();
-        listeners.add(new StorageListener(console));
-        setThermostatVars(paths);
-    }
-
-    private void setThermostatVars(CommonPaths paths) {
-        //set thermostat environment
-        createUserScript = paths.getSystemThermostatHome().toString() + "/lib/create-user.js";
-        userDoneFile = paths.getUserThermostatHome().toString() + "/data/mongodb-user-done.stamp";
-        webApp = paths.getSystemThermostatHome() + "/webapp";
-        String setupCompletePath = paths.getUserThermostatHome().toString() + "/data/setup-complete.stamp";
-        agentAuthFile = paths.getUserAgentAuthConfigFile();
-        setupCompleteFile = new File(setupCompletePath);
-
-        //set stamp complete vars
-        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss zzz");
-        Date date = new Date();
-        String timestamp = dateFormat.format(date);
-        String programName = "Thermostat Setup";
-        setupTmpUnlockContent = "Temporarily unlocked thermostat via " + programName + " on " + timestamp;
-        setupUnlockContentRegular = "Created by " + programName + " on " + timestamp;
-    }
-
-    //package-private for testing
-    void createCredentialFile(File file) throws IOException {
-        if (!file.exists()) {
-            //create file and set file permissions to 600
-            Files.createFile(file.toPath(), PosixFilePermissions.asFileAttribute(credFilePermissions));
-        }
-    }
-
-    @Override
-    public void createMongodbUser(String username, char[] password) throws MongodbUserSetupException {
-        try {
-            unlockThermostat();
-
-            startStorage();
-
-            byte[] encoded = Files.readAllBytes(Paths.get(createUserScript));
-            String mongoInput = new String(encoded);
-            mongoInput = mongoInput.replaceAll("\\$USERNAME", username);
-            mongoInput = mongoInput.replaceAll("\\$PASSWORD", String.valueOf(password));
-
-            File mongoInputFile = new File(MONGO_INPUT_SCRIPT);
-            mongoInputFile.deleteOnExit();
-            Files.write(mongoInputFile.toPath(), mongoInput.getBytes());
-
-            int mongoRetVal = runMongo();
-            if (mongoRetVal != 0) {
-                throw new MongodbUserSetupException("Mongodb user setup failed");
-            }
-
-            stopStorage();
-
-            if (isWebAppInstalled()) {
-                writeStorageCredentialsFile(username, password);
-            }
-
-            File userDoneFile = new File(this.userDoneFile);
-            userDoneFile.createNewFile();
-
-            Files.write(setupCompleteFile.toPath(), setupUnlockContentRegular.getBytes());
-        } catch (IOException | InterruptedException e) {
-            removeTempStampFile();
-            throw new MongodbUserSetupException("Error creating Mongodb user", e);
-        }
-    }
-
-    //package-private for testing
-    void unlockThermostat() throws IOException {
-        Files.write(setupCompleteFile.toPath(), setupTmpUnlockContent.getBytes());
-    }
-
-    private void startStorage() throws MongodbUserSetupException {
-        launcher.run(STORAGE_START_ARGS, listeners, false);
-
-        if (storageFailed) {
-            throw new MongodbUserSetupException("Thermostat storage failed to start");
-        }
-    }
-
-    private void stopStorage() throws MongodbUserSetupException {
-        launcher.run(STORAGE_STOP_ARGS, listeners, false);
-
-        if (storageFailed) {
-            throw new MongodbUserSetupException("Thermostat storage failed to stop");
-        }
-    }
-
-    //package-private for testing
-    int runMongo() throws IOException, InterruptedException {
-        ProcessBuilder mongoProcess = new ProcessBuilder("mongo", "127.0.0.1:27518/thermostat", MONGO_INPUT_SCRIPT);
-        return mongoProcess.start().waitFor();
-    }
-
-    private void writeStorageCredentialsFile(String username, char[] password) throws MongodbUserSetupException {
-        try {
-            Properties credentialProps = new Properties();
-            File credentialsFile = finder.getConfiguration(WEB_AUTH_FILE);
-            createCredentialFile(credentialsFile);
-            credentialProps.load(new FileInputStream(credentialsFile));
-            if (credentialProps.getProperty("username") == null) {
-                credentialProps = new Properties();
-                credentialProps.setProperty("username", username);
-                credentialProps.setProperty("password", String.valueOf(password));
-                credentialProps.store(new FileOutputStream(credentialsFile), "Storage Credentials");
-            }
-        } catch (IOException e) {
-            throw new MongodbUserSetupException("Storing credentials to file " + WEB_AUTH_FILE + " failed!", e);
-        }
-    }
-
-    private void removeTempStampFile() {
-        if (setupCompleteFile.exists()) {
-            setupCompleteFile.delete();
-        }
-    }
-
-    @Override
-    public void createThermostatUser(String username, char[] password, String[] roles) throws IOException {
-        List<String> rolesList = Arrays.asList(roles);
-
-        if (rolesList.containsAll(Arrays.asList(UserRoles.CLIENT_ROLES))) {
-            createClientUser(username, password, roles);
-        } else if (rolesList.containsAll(Arrays.asList(UserRoles.AGENT_ROLES))) {
-            createAgentUser(username, password, roles);
-        }
-    }
-
-    private void createClientUser(String username, char[] password, String[] roles) throws IOException {
-        Properties userProps = new Properties();
-        File userPropsFile = finder.getConfiguration(USERS_PROPERTIES);
-        createCredentialFile(userPropsFile);
-        userProps.load(new FileInputStream(userPropsFile));
-        if (userProps.getProperty(username) == null) {
-            userProps = new Properties();
-            userProps.setProperty(username, String.valueOf(password));
-            userProps.store(new FileOutputStream(userPropsFile, true), "Client User");
-        }
-
-        setClientRoles(username, roles);
-    }
-
-    private void setClientRoles(String username, String[] clientRoles) throws IOException {
-        String[] clientUserRoles = new String[] {
-                THERMOSTAT_CLIENT,
-                THERMOSTAT_CMDC,
-                UserRoles.PURGE
-        };
-
-        String[] cmdcRoles = new String[] {
-                UserRoles.GRANT_CMD_CHANNEL_GARBAGE_COLLECT,
-                UserRoles.GRANT_CMD_CHANNEL_DUMP_HEAP,
-                UserRoles.GRANT_CMD_CHANNEL_GRANT_THREAD_HARVESTER,
-                UserRoles.GRANT_CMD_CHANNEL_KILLVM,
-                UserRoles.GRANT_CMD_PROFILE_VM,
-                UserRoles.GRANT_CMD_CHANNEL_PING,
-                UserRoles.GRANT_CMD_CHANNEL_JMX_TOGGLE_NOTIFICATION,
-        };
-
-        setRoleProperty(username, clientUserRoles);
-        setRoleProperty(THERMOSTAT_CLIENT, clientRoles);
-        setRoleProperty(THERMOSTAT_CMDC, cmdcRoles);
-
-        File rolePropsFile = finder.getConfiguration(ROLES_PROPERTIES);
-        createCredentialFile(rolePropsFile);
-        if (roleProps.size() > 0) {
-            FileOutputStream roleStream = new FileOutputStream(rolePropsFile, true);
-            roleProps.store(new PropertiesWriter(roleStream), "Thermostat Client Roles");
-        }
-    }
-
-    private void createAgentUser(String username, char[] password, String[] roles) throws IOException {
-        Properties userProps = new Properties();
-        File userPropsFile = finder.getConfiguration(USERS_PROPERTIES);
-        createCredentialFile(userPropsFile);
-        userProps.load(new FileInputStream(userPropsFile));
-        if (userProps.getProperty(username) == null) {
-            userProps = new Properties();
-            userProps.setProperty(username, String.valueOf(password));
-            userProps.store(new FileOutputStream(userPropsFile, true), "Agent User");
-        }
-
-        //set agent credentials
-        Properties agentProps = new Properties();
-        createCredentialFile(agentAuthFile);
-        agentProps.load(new FileInputStream(agentAuthFile));
-        if (userProps.getProperty("username") == null) {
-            agentProps = new Properties();
-            agentProps.setProperty("username", username);
-            agentProps.setProperty("password", String.valueOf(password));
-            agentProps.store(new FileOutputStream(agentAuthFile), "Agent Credentials");
-        }
-
-        setAgentRoles(username, roles);
-    }
-
-    private void setAgentRoles(String username, String[] agentRoles) throws IOException {
-        String[] agentUserRoles = new String[] {
-                THERMOSTAT_AGENT
-        };
-        setRoleProperty(username, agentUserRoles);
-        setRoleProperty(THERMOSTAT_AGENT, agentRoles);
-        File rolePropsFile = finder.getConfiguration(ROLES_PROPERTIES);
-        createCredentialFile(rolePropsFile);
-        if (roleProps.size() > 0) {
-            FileOutputStream roleStream = new FileOutputStream(rolePropsFile, true);
-            roleProps.store(new PropertiesWriter(roleStream), "Thermostat Agent Roles");
-        }
-    }
-
-    private void setRoleProperty(String attribute, String[] roles) throws IOException {
-        Properties existingRoleProps = new Properties();
-        File rolePropsFile = finder.getConfiguration(ROLES_PROPERTIES);
-        createCredentialFile(rolePropsFile);
-        existingRoleProps.load(new FileInputStream(rolePropsFile));
-        if (roleProps == null) {
-            roleProps = new Properties();
-        }
-        if (roles.length > 0 && existingRoleProps.getProperty(attribute) == null) {
-            StringBuilder rolesBuilder = new StringBuilder();
-            for (int i = 0; i < roles.length - 1; i++) {
-                rolesBuilder.append(roles[i] + ", " + System.getProperty("line.separator"));
-            }
-            rolesBuilder.append(roles[roles.length - 1]);
-            roleProps.setProperty(attribute, rolesBuilder.toString());
-        }
-    }
-
-    @Override
-    public boolean isWebAppInstalled() {
-        Path webAppPath = Paths.get(webApp);
-        if (Files.exists(webAppPath)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * The Properties.store() method doesn't allow for new lines. This
-     * class is used so that when a property key has multiple associated
-     * values, they are written in a readable manner.
-     */
-    public static class PropertiesWriter extends PrintWriter {
-
-        private static final int INDENT_AMOUNT = 4;
-
-        public PropertiesWriter(OutputStream out) {
-            super(out);
-        }
-
-        @Override
-        public void write(char[] line, int startIdx, int len) {
-            for (int i = startIdx; i < len; i++) {
-                // interpret new lines as such
-                if (isNewLine(line, i)) {
-                    i++; // skip 'n' in \n
-                    try {
-                        out.write('\\');
-                        out.write(System.getProperty("line.separator"));
-                        // indent following lines
-                        for (int j = 0; j < INDENT_AMOUNT; j++) {
-                            out.write(' ');
-                        }
-                    } catch (IOException e) {
-                        e.printStackTrace(System.err);
-                    }
-                } else {
-                    try {
-                        out.write(line[i]);
-                    } catch (IOException e) {
-                        e.printStackTrace(System.err);
-                    }
-                }
-            }
-        }
-
-        private boolean isNewLine(char[] line, int j) {
-            if (j + 1 > line.length) {
-                return false;
-            }
-            if (line[j] == '\\' && line[j + 1] == 'n') {
-                return true;
-            }
-            return false;
-        }
-    }
-
-    private static class StorageListener implements ActionListener<ApplicationState> {
-
-        private final Console console;
-
-        private StorageListener(Console console) {
-            this.console = console;
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
-            if (actionEvent.getSource() instanceof AbstractStateNotifyingCommand) {
-                AbstractStateNotifyingCommand storage = (AbstractStateNotifyingCommand) actionEvent.getSource();
-                // Implementation detail: there is a single Storage instance registered
-                // as an OSGi service. We remove ourselves as listener so that we don't get
-                // notified in the case that the command is invoked by some other means later.
-                storage.getNotifier().removeActionListener(this);
-
-                switch (actionEvent.getActionId()) {
-                    case START:
-                        storageFailed = false;
-                        break;
-                    case FAIL:
-                        console.getOutput().println(translator.localize(LocaleResources.STORAGE_FAILED).getContents());
-                        storageFailed = true;
-                        break;
-                }
-            }
-        }
-    }
-}
--- a/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/UserRoles.java	Fri Aug 21 15:52:23 2015 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-/*
- * Copyright 2012-2015 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.setup.command.internal;
-
-public interface UserRoles {
-
-    /**
-     * Allows for a user to read records tied to any host.
-     */
-    final String GRANT_HOSTS_READ_ALL = "thermostat-hosts-grant-read-hostname-ALL";
-    /**
-     * Allows for a user to read records tied to any JVM id.
-     */
-    final String GRANT_VMS_READ_BY_VM_ID_ALL = "thermostat-vms-grant-read-vmId-ALL";
-    /**
-     * Allows for a user to read records tied to any username the JVM is running as.
-     */
-    final String GRANT_VMS_READ_BY_USERNAME_ALL = "thermostat-vms-grant-read-username-ALL";
-    /**
-     * Allows for a user to read any file from storage.
-     */
-    final String GRANT_FILES_READ_ALL = "thermostat-files-grant-read-filename-ALL";
-    /**
-     * Allows for a user to write any file to storage.
-     */
-    final String GRANT_FILES_WRITE_ALL = "thermostat-files-grant-write-filename-ALL";
-    /**
-     * Allows for a user to see records tied to any agent.
-     */
-    final String GRANT_AGENTS_READ_ALL = "thermostat-agents-grant-read-agentId-ALL";
-
-    final String GRANT_CMD_CHANNEL_GARBAGE_COLLECT = "thermostat-cmdc-grant-garbage-collect";
-    final String GRANT_CMD_CHANNEL_DUMP_HEAP = "thermostat-cmdc-grant-dump-heap";
-    final String GRANT_CMD_CHANNEL_GRANT_THREAD_HARVESTER = "thermostat-cmdc-grant-thread-harvester";
-    final String GRANT_CMD_CHANNEL_KILLVM = "thermostat-cmdc-grant-killvm";
-    final String GRANT_CMD_CHANNEL_PING = "thermostat-cmdc-grant-ping";
-    final String GRANT_CMD_CHANNEL_JMX_TOGGLE_NOTIFICATION = "thermostat-cmdc-grant-jmx-toggle-notifications";
-    final String GRANT_CMD_PROFILE_VM = "thermostat-cmdc-grant-profile-vm";
-
-    final String PREPARE_STATEMENT = "thermostat-prepare-statement";
-    final String READ = "thermostat-query";
-    final String WRITE = "thermostat-write";
-    final String LOAD_FILE = "thermostat-load-file";
-    final String SAVE_FILE = "thermostat-save-file";
-    final String PURGE = "thermostat-purge";
-    final String REGISTER_CATEGORY = "thermostat-register-category";
-    final String CMD_CHANNEL_VERIFY = "thermostat-cmdc-verify";
-    final String CMD_CHANNEL_GENERATE = "thermostat-cmdc-generate";
-    final String LOGIN = "thermostat-login";
-    final String ACCESS_REALM = "thermostat-realm";
-    
-    /**
-     * Basic role memberships for agent users
-     */
-    final String[] AGENT_ROLES = {
-            UserRoles.PREPARE_STATEMENT,
-            UserRoles.WRITE,
-            UserRoles.CMD_CHANNEL_VERIFY,
-            UserRoles.LOGIN,
-            UserRoles.PURGE,
-            UserRoles.REGISTER_CATEGORY,
-            UserRoles.ACCESS_REALM,
-            UserRoles.SAVE_FILE,
-    };
-
-    /**
-     * Basic role memberships for client users
-     */
-    final String[] CLIENT_ROLES = {
-            UserRoles.ACCESS_REALM,
-            UserRoles.LOGIN,
-            UserRoles.READ,
-            UserRoles.PREPARE_STATEMENT,
-            UserRoles.CMD_CHANNEL_GENERATE,
-            UserRoles.LOAD_FILE,
-            UserRoles.REGISTER_CATEGORY
-    };
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/CredentialFinder.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import com.redhat.thermostat.shared.config.CommonPaths;
+
+import java.io.File;
+import java.io.IOException;
+
+public class CredentialFinder {
+
+    private final CommonPaths paths;
+
+    public CredentialFinder(CommonPaths paths) {
+        this.paths = paths;
+    }
+
+    public File getConfiguration(String name) {
+        File systemFile = getConfigurationFile(paths.getSystemConfigurationDirectory(), name);
+        if (isUsable(systemFile)) {
+            return systemFile;
+        }
+        File userFile = getConfigurationFile(paths.getUserConfigurationDirectory(), name);
+        if (isUsable(userFile)) {
+            return userFile;
+        }
+        return null;
+    }
+
+    //package-private for testing
+    File getConfigurationFile(File directory, String name) {
+        return new File(directory, name);
+    }
+
+    //package-private for testing
+    boolean isUsable(File file) {
+        if (file.exists()) {
+            return file.isFile() && file.canRead() && file.canWrite();
+        } else {
+            try {
+                //Check that the parent directory is not read-only,
+                //so file can be created
+                File canonicalFile = file.getCanonicalFile();
+                return canonicalFile.getParentFile().canWrite();
+            } catch (IOException e) {
+                return false;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/CredentialsFileCreator.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.EnumSet;
+import java.util.Set;
+
+class CredentialsFileCreator {
+    
+    private static final Set<PosixFilePermission> CREDS_FILE_PERMISSIONS = EnumSet.of(
+            PosixFilePermission.OWNER_READ,
+            PosixFilePermission.OWNER_WRITE
+    );
+
+    void create(File file) throws IOException {
+        if (!file.exists()) {
+            //create file and set file permissions to 600
+            Files.createFile(file.toPath(), PosixFilePermissions.asFileAttribute(CREDS_FILE_PERMISSIONS));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/MongodbUserSetup.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.lang.ProcessBuilder.Redirect;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Properties;
+import java.util.Scanner;
+
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
+import com.redhat.thermostat.common.cli.Console;
+import com.redhat.thermostat.common.tools.ApplicationState;
+import com.redhat.thermostat.launcher.Launcher;
+import com.redhat.thermostat.setup.command.locale.LocaleResources;
+import com.redhat.thermostat.shared.config.CommonPaths;
+import com.redhat.thermostat.shared.locale.Translate;
+
+class MongodbUserSetup implements UserSetup {
+
+    static final String[] STORAGE_START_ARGS = {"storage", "--start", "--permitLocalhostException"};
+    static final String[] STORAGE_STOP_ARGS = {"storage", "--stop"};
+    private static final String WEB_AUTH_FILE = "web.auth";
+    private static boolean storageFailed = false;
+    private final UserCredsValidator validator;
+    private final Launcher launcher;
+    private final CredentialFinder finder;
+    private final CredentialsFileCreator fileCreator;
+    private final List<ActionListener<ApplicationState>> listeners;
+    private final CommonPaths paths;
+    private final StampFiles stampFiles;
+    private final StructureInformation structureInfo;
+    private String username;
+    private char[] password;
+    private String userComment;
+    
+    MongodbUserSetup(UserCredsValidator validator, Launcher launcher, CredentialFinder finder, CredentialsFileCreator fileCreator, Console console, CommonPaths paths, StampFiles stampFiles, StructureInformation structureInfo) {
+        this.validator = validator;
+        this.launcher = launcher;
+        this.finder = finder;
+        this.fileCreator = fileCreator;
+        this.stampFiles = stampFiles;
+        this.listeners = new ArrayList<>();
+        this.listeners.add(new StorageListener(console));
+        this.paths = paths;
+        this.structureInfo = structureInfo;
+    }
+    
+    @Override
+    public void createUser(String username, char[] password, String comment) {
+        validator.validateUsername(username);
+        validator.validatePassword(password);
+        this.username = username;
+        this.password = password;
+        this.userComment = comment;
+    }
+
+    @Override
+    public void commit() throws IOException {
+        try {
+            addMongodbUser();
+        } catch (MongodbUserSetupException e) {
+            throw new IOException(e);
+        }
+    }
+    
+    private void addMongodbUser() throws MongodbUserSetupException {
+        try {
+            unlockThermostat();
+
+            startStorage();
+
+            int mongoRetVal = runMongo();
+            if (mongoRetVal != 0) {
+                stampFiles.deleteSetupCompleteStamp();
+                stampFiles.deleteMongodbUserStamp();
+                throw new MongodbUserSetupException("Mongodb user setup failed");
+            }
+
+            if (isWebAppInstalled()) {
+                writeStorageCredentialsFile(username, password, userComment);
+            }
+
+            stampFiles.createMongodbUserStamp();
+
+            if (!isWebAppInstalled()) {
+                String completeDate = ThermostatSetup.DATE_FORMAT.format(new Date());
+                String regularContent = "Created by '" + ThermostatSetup.PROGRAM_NAME + "' on " + completeDate;
+                stampFiles.createSetupCompleteStamp(regularContent);
+            }
+        } catch (IOException | InterruptedException e) {
+            stampFiles.deleteSetupCompleteStamp();
+            stampFiles.deleteMongodbUserStamp();
+            throw new MongodbUserSetupException("Error creating Mongodb user", e);
+        } finally {
+            if (!storageFailed) {
+                stopStorage();
+            }
+            Arrays.fill(password, '\0'); // clear the password
+        }
+    }
+    
+    //package-private for testing
+    void unlockThermostat() throws IOException {
+        Date date = new Date();
+        String timestamp = ThermostatSetup.DATE_FORMAT.format(date);
+        String setupTmpUnlockContent = "Temporarily unlocked thermostat via '" + ThermostatSetup.PROGRAM_NAME + "' on " + timestamp + "\n";
+        stampFiles.createSetupCompleteStamp(setupTmpUnlockContent);
+    }
+    
+    //package-private for testing
+    int runMongo() throws IOException, InterruptedException {
+        ProcessBuilder mongoProcessBuilder = new ProcessBuilder("mongo", "127.0.0.1:27518/thermostat");
+        mongoProcessBuilder.redirectInput(Redirect.PIPE);
+        Process process = mongoProcessBuilder.start();
+        File createUserTemplate = new File(paths.getSystemLibRoot(), "create-user.js");
+        // Write to the forked processes stdIn replacing username/password
+        // on the fly.
+        try (OutputStream pOut = process.getOutputStream();
+             PrintWriter outWriter = new PrintWriter(pOut);
+             FileInputStream fin = new FileInputStream(createUserTemplate);
+             Scanner inScanner = new Scanner(fin)) {
+            while (inScanner.hasNextLine()) {
+                String line = inScanner.nextLine();
+                line = line.replaceAll("\\$USERNAME", username);
+                line = line.replaceAll("\\$PASSWORD", String.valueOf(password));
+                line = line + "\n";
+                outWriter.write(line);
+            }
+        }
+        return process.waitFor();
+    }
+    
+    private void startStorage() throws MongodbUserSetupException {
+        launcher.run(STORAGE_START_ARGS, listeners, false);
+
+        if (storageFailed) {
+            throw new MongodbUserSetupException("Thermostat storage failed to start");
+        }
+    }
+
+    private void stopStorage() throws MongodbUserSetupException {
+        launcher.run(STORAGE_STOP_ARGS, listeners, false);
+
+        if (storageFailed) {
+            throw new MongodbUserSetupException("Thermostat storage failed to stop");
+        }
+    }
+    
+    private boolean isWebAppInstalled() {
+        return structureInfo.isWebAppInstalled();
+    }
+    
+    private void writeStorageCredentialsFile(String username, char[] password, String comment) throws MongodbUserSetupException {
+        try {
+            Properties credentialProps = new Properties();
+            credentialProps.setProperty("storage.username", username);
+            credentialProps.setProperty("storage.password", String.valueOf(password));
+            File credentialsFile = finder.getConfiguration(WEB_AUTH_FILE);
+            fileCreator.create(credentialsFile);
+            credentialProps.store(new FileOutputStream(credentialsFile), comment);
+        } catch (IOException e) {
+            throw new MongodbUserSetupException("Storing credentials to file " + WEB_AUTH_FILE + " failed!", e);
+        }
+    }
+    
+    private static class StorageListener implements ActionListener<ApplicationState> {
+
+        private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+        private final Console console;
+
+        private StorageListener(Console console) {
+            this.console = console;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+            if (actionEvent.getSource() instanceof AbstractStateNotifyingCommand) {
+                AbstractStateNotifyingCommand storage = (AbstractStateNotifyingCommand) actionEvent.getSource();
+                // Implementation detail: there is a single Storage instance registered
+                // as an OSGi service. We remove ourselves as listener so that we don't get
+                // notified in the case that the command is invoked by some other means later.
+                storage.getNotifier().removeActionListener(this);
+
+                switch (actionEvent.getActionId()) {
+                    case START:
+                        storageFailed = false;
+                        break;
+                    case FAIL:
+                        console.getOutput().println(translator.localize(LocaleResources.STORAGE_FAILED).getContents());
+                        storageFailed = true;
+                        break;
+                }
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/MongodbUserSetupException.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+public class MongodbUserSetupException extends Exception {
+
+    public MongodbUserSetupException(String message) {
+        super(message);
+    }
+
+    public MongodbUserSetupException(Throwable cause) {
+        super(cause);
+    }
+
+    public MongodbUserSetupException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/PersistableSetup.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import java.io.IOException;
+
+interface PersistableSetup {
+
+    /**
+     * Persists setup info to disk.
+     * 
+     * @throws IOException
+     */
+    void commit() throws IOException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/PropertiesWriter.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+/**
+ * The Properties.store() method doesn't allow for new lines. This
+ * class is used so that when a property key has multiple associated
+ * values, they are written in a readable manner.
+ */
+public class PropertiesWriter extends PrintWriter {
+
+    private static final int INDENT_AMOUNT = 4;
+
+    public PropertiesWriter(OutputStream out) {
+        super(out);
+    }
+
+    @Override
+    public void write(char[] line, int startIdx, int len) {
+        for (int i = startIdx; i < len; i++) {
+            // interpret new lines as such
+            if (isNewLine(line, i)) {
+                i++; // skip 'n' in \n
+                try {
+                    out.write('\\');
+                    out.write(System.getProperty("line.separator"));
+                    // indent following lines
+                    for (int j = 0; j < INDENT_AMOUNT; j++) {
+                        out.write(' ');
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace(System.err);
+                }
+            } else {
+                try {
+                    out.write(line[i]);
+                } catch (IOException e) {
+                    e.printStackTrace(System.err);
+                }
+            }
+        }
+    }
+
+    private boolean isNewLine(char[] line, int j) {
+        if (j + 1 > line.length) {
+            return false;
+        }
+        if (line[j] == '\\' && line[j + 1] == 'n') {
+            return true;
+        }
+        return false;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/RoleSetup.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+interface RoleSetup extends PersistableSetup {
+
+    /**
+     * Assigns user {@code username} roles as given by {@code roles}.
+     * 
+     * It is the responsibility of the user to first define {@code username}
+     * via {@link #createThermostatUser(String, char[])}.
+     * 
+     * @param username
+     * @param roles
+     * @param comment An optional comment added to persistent files. May be
+     *                {@code null}
+     */
+    void assignRolesToUser(String username, String[] roles, String comment);
+    
+    /**
+     * Defines a new recursive role with name {@code name} and with role
+     * primitives as specified in {@code rolePrimitives} . Note elements in
+     * {@code rolePrimitives} might itself be a recursive roles.
+     * 
+     * @param name
+     * @param rolePrimitives
+     * @param comment An optional comment added to persistent files. May be
+     *                {@code null}
+     */
+    void createRecursiveRole(String name, String[] rolePrimitives, String comment);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/StampFiles.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+import com.redhat.thermostat.shared.config.CommonPaths;
+
+class StampFiles {
+    
+    private final CommonPaths paths;
+    
+    StampFiles(CommonPaths paths) {
+        this.paths = paths;
+    }
+    
+    void createMongodbUserStamp() throws IOException {
+        File userDoneFile = getMongodbStampFile();
+        userDoneFile.createNewFile();
+    }
+    
+    void createSetupCompleteStamp(String contents) throws IOException {
+        File setupCompleteFile = getSetupCompleteStampFile();
+        Files.write(setupCompleteFile.toPath(), contents.getBytes());
+    }
+    
+    void deleteSetupCompleteStamp() {
+        File setupCompleteFile = getSetupCompleteStampFile();
+        deleteFile(setupCompleteFile);
+    }
+    
+    void deleteMongodbUserStamp() {
+        File userDoneFile = getMongodbStampFile();
+        deleteFile(userDoneFile);
+    }
+    
+    File getMongodbStampFile() {
+        return new File(paths.getUserPersistentDataDirectory(), "mongodb-user-done.stamp");
+    }
+    
+    File getSetupCompleteStampFile() {
+        return new File(paths.getUserPersistentDataDirectory(), "setup-complete.stamp");
+    }
+    
+    private void deleteFile(File file) {
+        try {
+            Files.delete(file.toPath());
+        } catch (IOException e) {
+            // ignore, wanted it deleted.
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/StructureInformation.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import com.redhat.thermostat.shared.config.CommonPaths;
+
+class StructureInformation {
+    
+    private final String webApp;
+    
+    StructureInformation(CommonPaths paths) {
+        webApp = paths.getSystemThermostatHome() + "/webapp";
+    }
+
+    boolean isWebAppInstalled() {
+        Path webAppPath = Paths.get(webApp);
+        if (Files.exists(webAppPath)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/ThermostatSetup.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Properties;
+
+import com.redhat.thermostat.common.cli.Console;
+import com.redhat.thermostat.launcher.Launcher;
+import com.redhat.thermostat.shared.config.CommonPaths;
+
+public class ThermostatSetup implements PersistableSetup {
+
+    static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss zzz");
+    static final String PROGRAM_NAME = "thermostat setup";
+    private static final String THERMOSTAT_AGENT_REC_ROLE_NAME = "thermostat-agent";
+    private static final String THERMOSTAT_CLIENT_REC_ROLE_NAME = "thermostat-client";
+    private static final String THERMOSTAT_GRANT_CMD_CHANNEL_ALL_REC_ROLE_NAME = "thermostat-cmdc";
+    private static final String THERMOSTAT_ADMIN_READ_ALL_REC_ROLE_NAME = "thermostat-admin-read-all";
+    private final ThermostatUserSetup userSetup;
+    private final MongodbUserSetup mongodbUserSetup;
+    private final StructureInformation structureInfo;
+    private final CredentialsFileCreator creator;
+    private final CommonPaths paths;
+    private String agentUserName;
+    private char[] agentPassword;
+    
+    ThermostatSetup(ThermostatUserSetup userSetup, MongodbUserSetup mongodbUserSetup, StructureInformation structureInfo, CommonPaths paths, CredentialsFileCreator creator) {
+        this.mongodbUserSetup = mongodbUserSetup;
+        this.userSetup = userSetup;
+        this.structureInfo = structureInfo;
+        this.paths = paths;
+        this.creator = creator;
+    }
+
+    public void createMongodbUser(String username, char[] password) {
+        mongodbUserSetup.createUser(username, password, "Backing storage connection credentials.");
+    }
+
+    public void createClientAdminUser(String username, char[] password) {
+        userSetup.createUser(username, password, "Thermostat client admin user");
+        userSetup.createRecursiveRole(THERMOSTAT_GRANT_CMD_CHANNEL_ALL_REC_ROLE_NAME,
+                                      UserRoles.CMD_CHANNEL_GRANT_ALL_ACTIONS,
+                                      "Recursive role granting all CMD-channel actions.");
+        userSetup.createRecursiveRole(THERMOSTAT_CLIENT_REC_ROLE_NAME,
+                                      UserRoles.CLIENT_ROLES,
+                                      "Recursive role for Thermostat client users.");
+        userSetup.createRecursiveRole(THERMOSTAT_ADMIN_READ_ALL_REC_ROLE_NAME,
+                                      UserRoles.ADMIN_READALL,
+                                      "Recursive role allowing a user to read all records.");
+        userSetup.assignRolesToUser(username, new String[] {
+                                                THERMOSTAT_CLIENT_REC_ROLE_NAME,
+                                                THERMOSTAT_GRANT_CMD_CHANNEL_ALL_REC_ROLE_NAME,
+                                                THERMOSTAT_ADMIN_READ_ALL_REC_ROLE_NAME,
+                                                UserRoles.PURGE // Client needs purge for clean-data cmd.
+                                              },
+                                    "Client admin user username => role assignment."
+        );
+    }
+
+    public void createAgentUser(String username, char[] password) {
+        userSetup.createUser(username, password, "Thermostat agent user");
+        userSetup.createRecursiveRole(THERMOSTAT_AGENT_REC_ROLE_NAME,
+                                      UserRoles.AGENT_ROLES,
+                                      "Recursive role for Thermostat agent users.");
+        userSetup.assignRolesToUser(username,
+                                    new String[] {
+                                        THERMOSTAT_AGENT_REC_ROLE_NAME,
+                                        UserRoles.GRANT_FILES_WRITE_ALL // Agent needs perm to write files.
+                                    },
+                                    "Agent user username => role assignment.");
+        // Hold on to creds for persistency later.
+        agentUserName = username;
+        agentPassword = password;
+    }
+
+    @Override
+    public void commit() throws IOException {
+        // FIXME: report errors
+        mongodbUserSetup.commit();
+        userSetup.commit();
+        writeAgentAuthFile();
+    }
+    
+    private void writeAgentAuthFile() throws IOException {
+        Properties credentialProps = new Properties();
+        credentialProps.setProperty("username", agentUserName);
+        credentialProps.setProperty("password", String.valueOf(agentPassword));
+        File credentialsFile = paths.getUserAgentAuthConfigFile();
+        creator.create(credentialsFile);
+        try (FileOutputStream fout = new FileOutputStream(credentialsFile)) {
+            credentialProps.store(fout, "Credentials used for 'thermostat agent' connections.");
+        }
+    }
+
+    public boolean isWebAppInstalled() {
+        return structureInfo.isWebAppInstalled();
+    }
+    
+    public static ThermostatSetup create(Launcher launcher, CommonPaths paths, Console console) {
+        CredentialFinder finder = new CredentialFinder(paths);
+        CredentialsFileCreator creator = new CredentialsFileCreator();
+        StampFiles stampFiles = new StampFiles(paths);
+        StructureInformation info = new StructureInformation(paths);
+        MongodbUserSetup mongoSetup = new MongodbUserSetup(new UserCredsValidator(), launcher, finder, creator , console, paths, stampFiles, info);
+        ThermostatUserSetup userSetup = new ThermostatUserSetup(new UserPropertiesFinder(finder), new UserCredsValidator(), creator, stampFiles);
+        return new ThermostatSetup(userSetup, mongoSetup, info, paths, creator);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/ThermostatUserSetup.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+class ThermostatUserSetup implements UserSetup, RoleSetup {
+    
+    private static final Logger logger = LoggingUtils.getLogger(ThermostatUserSetup.class);
+    private final UserCredsValidator validator;
+    private final UserPropertiesFinder propsFileFinder;
+    private final CredentialsFileCreator fileCreator;
+    private final StampFiles stampFiles;
+    private final Map<String, String> roleComments = new HashMap<>();
+    private final Map<String, String> userComments = new HashMap<>();
+    private final Map<String, String[]> roles = new HashMap<>();
+    private final Map<String, char[]> userCreds = new HashMap<>();
+    
+    ThermostatUserSetup(UserPropertiesFinder propsFileFinder, UserCredsValidator validator, CredentialsFileCreator creator, StampFiles stampFiles) {
+        this.propsFileFinder = propsFileFinder;
+        this.validator = validator;
+        this.fileCreator = creator;
+        this.stampFiles = stampFiles;
+    }
+
+    @Override
+    public void commit() throws IOException {
+        // FIXME: Don't persist users/role setup if webapp is not installed.
+        Map<String, CommentedRolePropertyValue> roleProps = buildRoleProperties();
+        Map<String, CommentedCredsPropertyValue> userProps = buildUserProperties();
+        writeUsers(userProps);
+        writeRoles(roleProps);
+        writeSetupCompleteFile();
+    }
+    
+    private void writeSetupCompleteFile() throws IOException {
+        String completeTime = ThermostatSetup.DATE_FORMAT.format(new Date());
+        String contents = "Created by '" + ThermostatSetup.PROGRAM_NAME + "' on " + completeTime + "\n";
+        stampFiles.createSetupCompleteStamp(contents);
+    }
+    
+    // package-private for testing
+    void writeRoles(Map<String, CommentedRolePropertyValue> roleProps) throws IOException {
+        File rolePropsFile = propsFileFinder.getRolesProperties();
+        fileCreator.create(rolePropsFile);
+        Properties existingProperties = readPropsFromFile(rolePropsFile);
+        try (FileOutputStream roleStream = new FileOutputStream(rolePropsFile, true);
+                Writer rolesWriter = new PropertiesWriter(roleStream)) {
+            for (String key : roleProps.keySet()) {
+                Properties p = new Properties();
+                CommentedRolePropertyValue val = roleProps.get(key);
+                // prevent duplicate entries from being written
+                if (existingProperties.get(key) == null) {
+                    p.put(key, val.getValue());
+                    p.store(rolesWriter, val.getComment());
+                } else {
+                    logger.info("Skipping already existing key '" + key + "' in file " + rolePropsFile.toString());
+                }
+            }
+        }
+        
+    }
+
+    // package-private for testing
+    Properties readPropsFromFile(File propsFile) {
+        Properties props = new Properties();
+        try (FileInputStream inStream = new FileInputStream(propsFile)) {
+            props.load(inStream);
+        } catch (IOException e) {
+            logger.log(Level.INFO, "Failed to read existing properties file " + propsFile.toString());
+        }
+        return props;
+    }
+
+    // package-private for testing
+    void writeUsers(Map<String, CommentedCredsPropertyValue> userProps) throws IOException {
+        File userPropsFile = propsFileFinder.getUserProperties();
+        fileCreator.create(userPropsFile);
+        Properties existingProperties = readPropsFromFile(userPropsFile);
+        try (FileOutputStream userStream = new FileOutputStream(userPropsFile, true)) {
+            for (String key : userProps.keySet()) {
+                Properties p = new Properties();
+                CommentedCredsPropertyValue val = userProps.get(key);
+                // prevent duplicate entries from being written
+                if (existingProperties.get(key) == null) {
+                    p.put(key, String.valueOf(val.getValue()));
+                    p.store(userStream, val.getComment());
+                } else {
+                    logger.info("Skipping already existing key '" + key + "' in file " + userPropsFile.toString());
+                }
+            }
+        }
+    }
+
+    // package-private for testing
+    Map<String, CommentedCredsPropertyValue> buildUserProperties() {
+        Map<String, CommentedCredsPropertyValue> userProps = new HashMap<>();
+        for (String user: userCreds.keySet()) {
+            userProps.put(user, new CommentedCredsPropertyValue(userCreds.get(user), userComments.get(user)));
+        }
+        return userProps;
+    }
+
+    // package-private for testing
+    Map<String, CommentedRolePropertyValue> buildRoleProperties() {
+        Map<String, CommentedRolePropertyValue> roleProps = new HashMap<>();
+        for (String roleKey: roles.keySet()) {
+            String value = roleSetToString(roles.get(roleKey));
+            CommentedRolePropertyValue roleVal = new CommentedRolePropertyValue(value, roleComments.get(roleKey));
+            roleProps.put(roleKey, roleVal);
+        }
+        return roleProps;
+    }
+
+    @Override
+    public void assignRolesToUser(String username, String[] roles, String comment) {
+        validator.validateUsername(username);
+        validateRoles(roles);
+        this.roles.put(username, roles);
+        addRoleComment(username, comment);
+    }
+
+    @Override
+    public void createRecursiveRole(String name, String[] rolePrimitives, String comment) {
+        validator.validateUsername(name);
+        validateRoles(rolePrimitives);
+        this.roles.put(name, rolePrimitives);
+        addRoleComment(name, comment);
+    }
+    
+    private void validateRoles(String[] roles) {
+        if (roles == null || roles.length == 0) {
+            throw new IllegalArgumentException("Roles must not be null or empty");
+        }
+    }
+
+    @Override
+    public void createUser(String username, char[] password, String comment) {
+        validator.validateUsername(username);
+        validator.validatePassword(password);
+        this.userCreds.put(username, password);
+        addUserComment(username, comment);
+    }
+    
+    void addRoleComment(String property, String comment) {
+        if (comment != null) {
+            roleComments.put(Objects.requireNonNull(property), comment);
+        }
+    }
+    
+    void addUserComment(String property, String comment) {
+        if (comment != null) {
+            userComments.put(Objects.requireNonNull(property), comment);
+        }
+    }
+    
+    String roleSetToString(String[] roles) {
+        if (roles.length == 0) {
+            return "";
+        }
+        StringBuilder rolesBuilder = new StringBuilder();
+        for (int i = 0; i < roles.length - 1; i++) {
+            rolesBuilder.append(roles[i] + ", " + System.getProperty("line.separator"));
+        }
+        rolesBuilder.append(roles[roles.length - 1]);
+        return rolesBuilder.toString();
+    }
+    
+    static class CommentedPropertyValue {
+        private final String comment;
+        
+        CommentedPropertyValue(String comment) {
+            this.comment = comment;
+        }
+
+        String getComment() {
+            return comment;
+        }
+
+    }
+    
+    static class CommentedRolePropertyValue extends CommentedPropertyValue {
+        
+        private final String value;
+        
+        CommentedRolePropertyValue(String value, String comment) {
+            super(comment);
+            this.value = value;
+        }
+
+        String getValue() {
+            return value;
+        }
+    }
+    
+    static class CommentedCredsPropertyValue extends CommentedPropertyValue {
+        
+        private final char[] value;
+        
+        CommentedCredsPropertyValue(char[] value, String comment) {
+            super(comment);
+            this.value = value;
+        }
+
+        char[] getValue() {
+            return value;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/UserCredsValidator.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+class UserCredsValidator {
+    
+    void validateUsername(String username) {
+        if (username == null || username.isEmpty()) {
+            throw new IllegalArgumentException("Name must not be null or empty");
+        }
+    }
+    
+    void validatePassword(char[] password) {
+        if (password == null || password.length == 0) {
+            throw new IllegalArgumentException("Password must not be null or empty");
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/UserPropertiesFinder.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import java.io.File;
+
+class UserPropertiesFinder {
+
+    private static final String USER_PROPERTIES = "thermostat-users.properties";
+    private static final String ROLES_PROPERTIES = "thermostat-roles.properties";
+    
+    private final CredentialFinder credsFinder;
+    
+    UserPropertiesFinder(CredentialFinder finder) {
+        this.credsFinder = finder;
+    }
+    
+    File getUserProperties() {
+        return credsFinder.getConfiguration(USER_PROPERTIES);
+    }
+    
+    File getRolesProperties() {
+        return credsFinder.getConfiguration(ROLES_PROPERTIES);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/UserRoles.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+public interface UserRoles {
+
+    /**
+     * Allows for a user to read records tied to any host.
+     */
+    final String GRANT_HOSTS_READ_ALL = "thermostat-hosts-grant-read-hostname-ALL";
+    /**
+     * Allows for a user to read records tied to any JVM id.
+     */
+    final String GRANT_VMS_READ_BY_VM_ID_ALL = "thermostat-vms-grant-read-vmId-ALL";
+    /**
+     * Allows for a user to read records tied to any username the JVM is running as.
+     */
+    final String GRANT_VMS_READ_BY_USERNAME_ALL = "thermostat-vms-grant-read-username-ALL";
+    /**
+     * Allows for a user to read any file from storage.
+     */
+    final String GRANT_FILES_READ_ALL = "thermostat-files-grant-read-filename-ALL";
+    /**
+     * Allows for a user to write any file to storage.
+     */
+    final String GRANT_FILES_WRITE_ALL = "thermostat-files-grant-write-filename-ALL";
+    /**
+     * Allows for a user to see records tied to any agent.
+     */
+    final String GRANT_AGENTS_READ_ALL = "thermostat-agents-grant-read-agentId-ALL";
+
+    final String GRANT_CMD_CHANNEL_GARBAGE_COLLECT = "thermostat-cmdc-grant-garbage-collect";
+    final String GRANT_CMD_CHANNEL_DUMP_HEAP = "thermostat-cmdc-grant-dump-heap";
+    final String GRANT_CMD_CHANNEL_GRANT_THREAD_HARVESTER = "thermostat-cmdc-grant-thread-harvester";
+    final String GRANT_CMD_CHANNEL_KILLVM = "thermostat-cmdc-grant-killvm";
+    final String GRANT_CMD_CHANNEL_PING = "thermostat-cmdc-grant-ping";
+    final String GRANT_CMD_CHANNEL_JMX_TOGGLE_NOTIFICATION = "thermostat-cmdc-grant-jmx-toggle-notifications";
+    final String GRANT_CMD_PROFILE_VM = "thermostat-cmdc-grant-profile-vm";
+
+    final String PREPARE_STATEMENT = "thermostat-prepare-statement";
+    final String READ = "thermostat-query";
+    final String WRITE = "thermostat-write";
+    final String LOAD_FILE = "thermostat-load-file";
+    final String SAVE_FILE = "thermostat-save-file";
+    final String PURGE = "thermostat-purge";
+    final String REGISTER_CATEGORY = "thermostat-register-category";
+    final String CMD_CHANNEL_VERIFY = "thermostat-cmdc-verify";
+    final String CMD_CHANNEL_GENERATE = "thermostat-cmdc-generate";
+    final String LOGIN = "thermostat-login";
+    final String ACCESS_REALM = "thermostat-realm";
+    
+    /**
+     * Basic role memberships for agent users
+     */
+    final String[] AGENT_ROLES = {
+            UserRoles.PREPARE_STATEMENT,
+            UserRoles.WRITE,
+            UserRoles.CMD_CHANNEL_VERIFY,
+            UserRoles.LOGIN,
+            UserRoles.PURGE,
+            UserRoles.REGISTER_CATEGORY,
+            UserRoles.ACCESS_REALM,
+            UserRoles.SAVE_FILE,
+    };
+
+    /**
+     * Basic role memberships for client users
+     */
+    final String[] CLIENT_ROLES = {
+            UserRoles.ACCESS_REALM,
+            UserRoles.LOGIN,
+            UserRoles.READ,
+            UserRoles.PREPARE_STATEMENT,
+            UserRoles.CMD_CHANNEL_GENERATE,
+            UserRoles.LOAD_FILE,
+            UserRoles.REGISTER_CATEGORY
+    };
+    
+    final String[] ADMIN_READALL = new String[] {
+            UserRoles.GRANT_FILES_READ_ALL,
+            UserRoles.GRANT_HOSTS_READ_ALL,
+            UserRoles.GRANT_VMS_READ_BY_USERNAME_ALL,
+            UserRoles.GRANT_VMS_READ_BY_VM_ID_ALL,
+            UserRoles.GRANT_AGENTS_READ_ALL
+    };
+
+    final String[] CMD_CHANNEL_GRANT_ALL_ACTIONS = new String[] {
+            UserRoles.GRANT_CMD_CHANNEL_DUMP_HEAP,
+            UserRoles.GRANT_CMD_CHANNEL_GARBAGE_COLLECT,
+            UserRoles.GRANT_CMD_CHANNEL_GRANT_THREAD_HARVESTER,
+            UserRoles.GRANT_CMD_CHANNEL_JMX_TOGGLE_NOTIFICATION,
+            UserRoles.GRANT_CMD_CHANNEL_KILLVM,
+            UserRoles.GRANT_CMD_CHANNEL_PING,
+            UserRoles.GRANT_CMD_PROFILE_VM
+    };
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/UserSetup.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+
+interface UserSetup extends PersistableSetup {
+    
+    /**
+     * Defines a user {@code username} with password {@code password}.
+     * 
+     * @param username
+     * @param password
+     * @param comment An optional comment added to persistent files. May be
+     *                {@code null}
+     */
+    void createUser(String username, char[] password, String comment);
+
+}
--- a/setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/CredentialFinderTest.java	Fri Aug 21 15:52:23 2015 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,227 +0,0 @@
-/*
- * Copyright 2012-2015 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.setup.command.internal;
-
-import com.redhat.thermostat.shared.config.CommonPaths;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class CredentialFinderTest {
-    private File systemConfigDir;
-    private File userConfigDir;
-
-    private File realFile1;
-    private File realFile2;
-    private File fileToCreate1;
-    private File fileToCreate2;
-
-    private CommonPaths paths;
-
-    @Before
-    public void setUp() throws IOException {
-        systemConfigDir = Files.createTempDirectory("system-config-dir").toFile();
-        userConfigDir = Files.createTempDirectory("user-config-dir").toFile();
-        fileToCreate1 = new File(systemConfigDir.toString(), "does-not-exist");
-        fileToCreate2 = new File(userConfigDir.toString(), "does-not-exist");
-
-        realFile1 = Files.createTempFile("credentialfinder-unit-test", null).toFile();
-        realFile2 = Files.createTempFile("credentialfinder-unit-test", null).toFile();
-
-        paths = mock(CommonPaths.class);
-        when(paths.getSystemConfigurationDirectory()).thenReturn(systemConfigDir);
-        when(paths.getUserConfigurationDirectory()).thenReturn(userConfigDir);
-    }
-
-    @After
-    public void tearDown() {
-        systemConfigDir.delete();
-        userConfigDir.delete();
-        realFile1.delete();
-        realFile2.delete();
-    }
-
-    @Test
-    public void verifyFileFromUserHomeIsUsedIfSystemHomeIsNotUsable() throws IOException {
-        CredentialFinder finder = new CredentialFinder(paths) {
-            @Override
-            File getConfigurationFile(File directory, String name) {
-                if (directory == systemConfigDir) {
-                    return new File("/does-not-exist/really-it-doesn't");
-                } else if (directory == userConfigDir) {
-                    return realFile1;
-                }
-                throw new AssertionError("Unknown test case");
-            }
-        };
-
-        File config = finder.getConfiguration("web.auth");
-        assertEquals(realFile1, config);
-    }
-
-    @Test
-    public void verifyFileFromSystemHomeIsUsedIfBothAreUsable() throws IOException {
-        final File systemFile = realFile1;
-        final File userFile = realFile2;
-
-        CredentialFinder finder = new CredentialFinder(paths) {
-            @Override
-            File getConfigurationFile(File directory, String name) {
-                if (directory == systemConfigDir) {
-                    return systemFile;
-                } else if (directory == userConfigDir) {
-                    return userFile;
-                }
-                throw new AssertionError("Unknown test case");
-            }
-        };
-
-        File config = finder.getConfiguration("web.auth");
-        assertEquals(systemFile, config);
-    }
-
-    @Test
-    public void verifyFileFromSystemHomeIsUsedIfUserHomeNotUsable() throws IOException {
-        CredentialFinder finder = new CredentialFinder(paths) {
-            @Override
-            File getConfigurationFile(File directory, String name) {
-                if (directory == systemConfigDir) {
-                    return realFile1;
-                } else if (directory == userConfigDir) {
-                    return new File("/does-not-exist/really-it-doesn't");
-                }
-                throw new AssertionError("Unknown test case");
-            }
-        };
-
-        File config = finder.getConfiguration("web.auth");
-        assertEquals(realFile1, config);
-    }
-
-
-    @Test
-    public void verifyFileFromSystemHomeIsUsedIfFileDoesNotExist() throws IOException {
-        CredentialFinder finder = new CredentialFinder(paths) {
-            @Override
-            File getConfigurationFile(File directory, String name) {
-                if (directory == systemConfigDir) {
-                    return fileToCreate1;
-                } else if (directory == userConfigDir) {
-                    return fileToCreate2;
-                }
-                throw new AssertionError("Unknown test case");
-            }
-        };
-
-        File config = finder.getConfiguration("web.auth");
-        assertEquals(fileToCreate1, config);
-    }
-
-    @Test
-    public void verifyFileFromUserHomeIsUsedIfSystemHomeIsNotUsableAndFileDoesNotExist() throws IOException {
-        systemConfigDir.setReadOnly();
-
-        CredentialFinder finder = new CredentialFinder(paths) {
-            @Override
-            File getConfigurationFile(File directory, String name) {
-                if (directory == systemConfigDir) {
-                    return fileToCreate1;
-                } else if (directory == userConfigDir) {
-                    return fileToCreate2;
-                }
-                throw new AssertionError("Unknown test case");
-            }
-        };
-
-        File config = finder.getConfiguration("web.auth");
-        assertEquals(fileToCreate2, config);
-    }
-
-    @Test
-    public void verifyIsNotUsableWhenNotIsFile() throws IOException {
-        File mockFile = mock(File.class);
-        when(mockFile.exists()).thenReturn(true);
-        when(mockFile.isFile()).thenReturn(false);
-        when(mockFile.canRead()).thenReturn(true);
-        when(mockFile.canWrite()).thenReturn(true);
-        CredentialFinder finder = new CredentialFinder(paths);
-
-        assertFalse(finder.isUsable(mockFile));
-    }
-
-    @Test
-    public void verifyIsNotUsableWhenNotCanRead() throws IOException {
-        File mockFile = mock(File.class);
-        when(mockFile.exists()).thenReturn(true);
-        when(mockFile.isFile()).thenReturn(true);
-        when(mockFile.canRead()).thenReturn(false);
-        when(mockFile.canWrite()).thenReturn(true);
-        CredentialFinder finder = new CredentialFinder(paths);
-
-        assertFalse(finder.isUsable(mockFile));
-    }
-
-    @Test
-    public void verifyIsNotUsableWhenNotCanWrite() throws IOException {
-        File mockFile = mock(File.class);
-        when(mockFile.exists()).thenReturn(true);
-        when(mockFile.isFile()).thenReturn(true);
-        when(mockFile.canRead()).thenReturn(true);
-        when(mockFile.canWrite()).thenReturn(false);
-        CredentialFinder finder = new CredentialFinder(paths);
-
-        assertFalse(finder.isUsable(mockFile));
-    }
-
-    @Test
-    public void verifyIsUsableWhenFileDoesNotExistAndHasNoParent() throws IOException {
-        File fileToCreate = new File("does-not-exist");
-        CredentialFinder finder = new CredentialFinder(paths);
-
-        assertTrue(finder.isUsable(fileToCreate));
-    }
-}
--- a/setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/ThermostatSetupImplTest.java	Fri Aug 21 15:52:23 2015 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,479 +0,0 @@
-/*
- * Copyright 2012-2015 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.setup.command.internal;
-
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.ActionNotifier;
-import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
-import com.redhat.thermostat.common.cli.Console;
-import com.redhat.thermostat.common.tools.ApplicationState;
-import com.redhat.thermostat.launcher.Launcher;
-import com.redhat.thermostat.shared.config.CommonPaths;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Properties;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.times;
-
-public class ThermostatSetupImplTest {
-
-    private Path testRoot;
-    private Path thermostatSysHome;
-    private Path thermostatUserHome;
-    private Path sysConfigDir;
-    private Path userConfigDir;
-    private Path userDataDir;
-    private Path userAgentAuth;
-    private Path credentialsFile;
-    private Path userPropertiesFile;
-    private Path rolesPropertiesFile;
-    private static final String username = "mongodevuser";
-    private static final String password = "mongodevpassword";
-    private static final String[] STORAGE_START_ARGS = {"storage", "--start", "--permitLocalhostException"};
-    private static final String[] STORAGE_STOP_ARGS = {"storage", "--stop"};
-    private static final String WEB_AUTH_FILE = "web.auth";
-    private static final String USERS_PROPERTIES = "thermostat-users.properties";
-    private static final String ROLES_PROPERTIES = "thermostat-roles.properties";
-    private static final String THERMOSTAT_AGENT = "thermostat-agent";
-
-    private ThermostatSetupImpl tSetup;
-    private CommonPaths paths;
-    private Launcher mockLauncher;
-    private ByteArrayOutputStream out;
-    private Console console;
-    private static ActionEvent<ApplicationState> mockActionEvent;
-    private static Collection<ActionListener<ApplicationState>> listeners;
-    private CredentialFinder mockCredentialFinder;
-
-    private void makeTempFilesAndDirectories() throws IOException {
-        testRoot = Files.createTempDirectory("thermostat");
-
-        thermostatSysHome = testRoot.resolve("system");
-        Files.createDirectory(thermostatSysHome);
-        thermostatUserHome = testRoot.resolve("user");
-        Files.createDirectory(thermostatUserHome);
-
-        sysConfigDir = thermostatSysHome.resolve("etc");
-        Files.createDirectories(sysConfigDir);
-        Path sysLibDir = thermostatSysHome.resolve("lib");
-        Files.createDirectories(sysLibDir);
-
-        userConfigDir = thermostatUserHome.resolve("etc");
-        Files.createDirectories(userConfigDir);
-        userDataDir = thermostatUserHome.resolve("data");
-        Files.createDirectories(userDataDir);
-
-        userAgentAuth = userConfigDir.resolve("agent.auth");
-        credentialsFile = sysConfigDir.resolve(WEB_AUTH_FILE);
-        userPropertiesFile = sysConfigDir.resolve(USERS_PROPERTIES);
-        rolesPropertiesFile = sysConfigDir.resolve(ROLES_PROPERTIES);
-
-        //create a dummy create-user.js
-        Path createUserScript = sysLibDir.resolve("create-user.js");
-        Files.write(createUserScript, new byte[]{});
-    }
-
-    @Before
-    public void setup() throws IOException, InterruptedException {
-        makeTempFilesAndDirectories();
-
-        out = new ByteArrayOutputStream();
-        console = mock(Console.class);
-        when(console.getOutput()).thenReturn(new PrintStream(out));
-
-        paths = mock(CommonPaths.class);
-        when(paths.getSystemThermostatHome()).thenReturn(thermostatSysHome.toFile());
-        when(paths.getUserThermostatHome()).thenReturn(thermostatUserHome.toFile());
-        when(paths.getUserAgentAuthConfigFile()).thenReturn(userAgentAuth.toFile());
-        when(paths.getSystemConfigurationDirectory()).thenReturn(sysConfigDir.toFile());
-        when(paths.getUserConfigurationDirectory()).thenReturn(userConfigDir.toFile());
-
-        mockLauncher = mock(Launcher.class);
-        mockActionEvent = mock(ActionEvent.class);
-        AbstractStateNotifyingCommand mockStorageCommand = mock(AbstractStateNotifyingCommand.class);
-        ActionNotifier<ApplicationState> mockNotifier = mock(ActionNotifier.class);
-        when(mockStorageCommand.getNotifier()).thenReturn(mockNotifier);
-        mockActionEvent = mock(ActionEvent.class);
-        when(mockActionEvent.getSource()).thenReturn(mockStorageCommand);
-        when(mockActionEvent.getPayload()).thenReturn(new String("Test String"));
-        mockCredentialFinder = mock(CredentialFinder.class);
-        when(mockCredentialFinder.getConfiguration(WEB_AUTH_FILE)).thenReturn(credentialsFile.toFile());
-        when(mockCredentialFinder.getConfiguration(USERS_PROPERTIES)).thenReturn(userPropertiesFile.toFile());
-        when(mockCredentialFinder.getConfiguration(ROLES_PROPERTIES)).thenReturn(rolesPropertiesFile.toFile());
-
-        tSetup = new ThermostatSetupImpl(mockLauncher, paths, console, mockCredentialFinder) {
-            @Override
-            int runMongo() {
-                //instead of running mongo through ProcessBuilder
-                //we need to always return 0 for success in tests
-                return 0;
-            }
-        };
-    }
-
-    @After
-    public void teardown() throws IOException {
-        paths = null;
-        mockLauncher = null;
-
-        Files.walkFileTree(testRoot, new SimpleFileVisitor<Path>() {
-            @Override
-            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                Files.delete(file);
-                return FileVisitResult.CONTINUE;
-            }
-
-            @Override
-            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
-                if (exc == null) {
-                    Files.delete(dir);
-                    return FileVisitResult.CONTINUE;
-                } else {
-                    throw exc;
-                }
-            }
-        });
-    }
-
-    @Test
-    public void testUnlockThermostatUnlockFileCreated() throws IOException {
-        String fileData;
-        File setupCompleteFile = new File(thermostatUserHome + "/data/setup-complete.stamp");
-        Path setupCompleteFilePath = Paths.get(setupCompleteFile.toString());
-
-        Files.createDirectories(setupCompleteFilePath.getParent());
-        tSetup.unlockThermostat();
-        fileData = new String(Files.readAllBytes(setupCompleteFilePath));
-
-        assertTrue(setupCompleteFile.exists());
-        assertTrue(fileData.contains("Temporarily unlocked"));
-    }
-
-    @Test
-    public void testSetupMongodbUser() throws IOException {
-        File userDoneFile = new File(userDataDir.toString() + "/mongodb-user-done.stamp");
-        File setupCompleteFile = new File(userDataDir.toString() + "/setup-complete.stamp");
-
-        //create path to webapp so web.auth creation is invoked
-        //when ThermostatSetup.createMongodbUser() is called
-        Path webAppPath = thermostatSysHome.resolve("webapp");
-        Files.createDirectories(webAppPath);
-
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                Object[] args = invocation.getArguments();
-                listeners = (Collection<ActionListener<ApplicationState>>) args[1];
-
-                when(mockActionEvent.getActionId()).thenReturn(ApplicationState.START);
-
-                for (ActionListener<ApplicationState> listener : listeners) {
-                    listener.actionPerformed(mockActionEvent);
-                }
-                return null;
-            }
-        }).when(mockLauncher).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
-
-        boolean exTriggered = false;
-        try {
-            tSetup.createMongodbUser(username, password.toCharArray());
-        } catch (MongodbUserSetupException e) {
-            exTriggered = true;
-        }
-
-        assertFalse(exTriggered);
-
-        verify(mockLauncher, times(1)).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
-        verify(mockLauncher, times(1)).run(eq(STORAGE_STOP_ARGS), isA(Collection.class), anyBoolean());
-        verify(mockActionEvent, times(1)).getActionId();
-
-        assertTrue(userDoneFile.exists());
-        assertTrue(setupCompleteFile.exists());
-
-        assertTrue(credentialsFile.toFile().exists());
-        String credentialsData = new String(Files.readAllBytes(credentialsFile));
-        assertTrue(credentialsData.contains("storage.username=" + username));
-        assertTrue(credentialsData.contains("storage.password=" + password));
-
-        String setupCompleteData = new String(Files.readAllBytes(setupCompleteFile.toPath()));
-        assertTrue(setupCompleteData.contains("Created by Thermostat Setup"));
-    }
-
-    @Test
-    public void testStorageStartFail() {
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                Object[] args = invocation.getArguments();
-                listeners = (Collection<ActionListener<ApplicationState>>) args[1];
-
-                when(mockActionEvent.getActionId()).thenReturn(ApplicationState.FAIL);
-
-                for (ActionListener<ApplicationState> listener : listeners) {
-                    listener.actionPerformed(mockActionEvent);
-                }
-                return null;
-            }
-        }).when(mockLauncher).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
-
-        try {
-            tSetup.createMongodbUser(username, password.toCharArray());
-            //shouldn't get here
-            fail();
-        } catch (MongodbUserSetupException e) {
-            assertTrue(e.getMessage().contains("Thermostat storage failed to start"));
-        }
-    }
-
-    @Test
-    public void testStorageStopFail() {
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                Object[] args = invocation.getArguments();
-                listeners = (Collection<ActionListener<ApplicationState>>) args[1];
-
-                when(mockActionEvent.getActionId()).thenReturn(ApplicationState.START);
-
-                for (ActionListener<ApplicationState> listener : listeners) {
-                    listener.actionPerformed(mockActionEvent);
-                }
-                return null;
-            }
-        }).when(mockLauncher).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
-
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                Object[] args = invocation.getArguments();
-                listeners = (Collection<ActionListener<ApplicationState>>) args[1];
-
-                when(mockActionEvent.getActionId()).thenReturn(ApplicationState.FAIL);
-
-                for (ActionListener<ApplicationState> listener : listeners) {
-                    listener.actionPerformed(mockActionEvent);
-                }
-                return null;
-            }
-        }).when(mockLauncher).run(eq(STORAGE_STOP_ARGS), isA(Collection.class), anyBoolean());
-
-        try {
-            tSetup.createMongodbUser(username, password.toCharArray());
-            //shouldn't get here
-            fail();
-        } catch (MongodbUserSetupException e) {
-            assertTrue(e.getMessage().contains("Thermostat storage failed to stop"));
-        }
-    }
-
-    @Test
-    public void testCreateMongodbUserFail() {
-        tSetup = new ThermostatSetupImpl(mockLauncher, paths, console, mockCredentialFinder) {
-            @Override
-            int runMongo() {
-                //return non-zero val to test failure
-                return 1;
-            }
-        };
-
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                Object[] args = invocation.getArguments();
-                listeners = (Collection<ActionListener<ApplicationState>>) args[1];
-
-                when(mockActionEvent.getActionId()).thenReturn(ApplicationState.START);
-
-                for (ActionListener<ApplicationState> listener : listeners) {
-                    listener.actionPerformed(mockActionEvent);
-                }
-                return null;
-            }
-        }).when(mockLauncher).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
-
-        try {
-            tSetup.createMongodbUser(username, password.toCharArray());
-            //shouldn't get here
-            fail();
-        } catch (MongodbUserSetupException e) {
-            assertTrue(e.getMessage().contains("Mongodb user setup failed"));
-        }
-    }
-
-    @Test
-    public void testSetupThermostatUser() throws IOException {
-        String clientUser = "client-tester";
-        String agentUser = "agent-tester";
-        String userPassword = "tester";
-
-        String[] agentRoles = new String[] {
-                    UserRoles.CMD_CHANNEL_VERIFY,
-                    UserRoles.LOGIN,
-                    UserRoles.PREPARE_STATEMENT,
-                    UserRoles.PURGE,
-                    UserRoles.REGISTER_CATEGORY,
-                    UserRoles.ACCESS_REALM,
-                    UserRoles.SAVE_FILE,
-                    UserRoles.WRITE,
-                    UserRoles.GRANT_FILES_WRITE_ALL,
-        };
-        String[] clientRoles = new String[] {
-                    UserRoles.GRANT_AGENTS_READ_ALL,
-                    UserRoles.CMD_CHANNEL_GENERATE,
-                    UserRoles.GRANT_HOSTS_READ_ALL,
-                    UserRoles.LOAD_FILE,
-                    UserRoles.LOGIN,
-                    UserRoles.PREPARE_STATEMENT,
-                    UserRoles.READ,
-                    UserRoles.ACCESS_REALM,
-                    UserRoles.REGISTER_CATEGORY,
-                    UserRoles.GRANT_VMS_READ_BY_USERNAME_ALL,
-                    UserRoles.GRANT_VMS_READ_BY_VM_ID_ALL,
-                    UserRoles.GRANT_FILES_READ_ALL,
-                    UserRoles.WRITE,
-        };
-
-        tSetup.createThermostatUser(agentUser, userPassword.toCharArray(), agentRoles);
-        tSetup.createThermostatUser(clientUser, userPassword.toCharArray(), clientRoles);
-
-        //check agent credentials file
-        assertTrue(userAgentAuth.toFile().exists());
-        String userAgentAuthData = new String(Files.readAllBytes(userAgentAuth));
-        assertTrue(userAgentAuthData.contains("username=agent-tester"));
-        assertTrue(userAgentAuthData.contains("password=tester"));
-
-        //check AgentUser file
-        assertTrue(userPropertiesFile.toFile().exists());
-
-        //check clientAdmin file
-        assertTrue(rolesPropertiesFile.toFile().exists());
-    }
-
-    @Test
-    public void testWebAppInstalledSuccess() throws IOException {
-        Path webAppPath = thermostatSysHome.resolve("webapp");
-        Files.createDirectories(webAppPath);
-        assertTrue(tSetup.isWebAppInstalled());
-    }
-
-    @Test
-    public void testWebAppInstalledFail() throws IOException {
-        //Call isWebAppInstalled() without creating
-        //a THERMOSTAT_SYS_HOME/webapp directory
-        assertFalse(tSetup.isWebAppInstalled());
-    }
-
-    @Test
-    public void testPropertiesWriter() throws IOException {
-        String key = THERMOSTAT_AGENT;
-        String[] roles  = new String[] {
-                UserRoles.LOGIN,
-                UserRoles.PREPARE_STATEMENT,
-                UserRoles.PURGE,
-                UserRoles.REGISTER_CATEGORY,
-        };
-        StringBuilder rolesBuilder = new StringBuilder();
-        for (int i = 0; i < roles.length - 1; i++) {
-            rolesBuilder.append(roles[i] + ", " + System.getProperty("line.separator"));
-        }
-        rolesBuilder.append(roles[roles.length - 1]);
-        String value = rolesBuilder.toString();
-
-        Properties propsToStore = new Properties();
-        propsToStore.setProperty(key, value);
-        FileOutputStream roleStream = new FileOutputStream(rolesPropertiesFile.toFile());
-        propsToStore.store(new ThermostatSetupImpl.PropertiesWriter(roleStream), null);
-
-        Properties propsToLoad = new Properties();
-        propsToLoad.load(new FileInputStream(rolesPropertiesFile.toFile()));
-        String[] loadedRoles = propsToLoad.getProperty(key).split(",\\s+");
-
-        assertTrue(Arrays.asList(roles).containsAll(Arrays.asList(loadedRoles)));
-    }
-
-    @Test
-    public void testCrendentialFileCreatedIfNotExists() throws IOException {
-        File agentAuthFile = mock(File.class);
-        when(agentAuthFile.exists()).thenReturn(false);
-        when(agentAuthFile.toPath()).thenReturn(userAgentAuth);
-
-        tSetup.createCredentialFile(agentAuthFile);
-
-        assertTrue(userAgentAuth.toFile().exists());
-    }
-
-    @Test
-    public void testCrendentialFileNotCreatedIfAlreadyExists() throws IOException {
-        File agentAuthFile = mock(File.class);
-        when(agentAuthFile.exists()).thenReturn(true);
-        when(agentAuthFile.toPath()).thenReturn(userAgentAuth);
-
-        tSetup.createCredentialFile(agentAuthFile);
-
-        assertFalse(userAgentAuth.toFile().exists());
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/CredentialFinderTest.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import com.redhat.thermostat.setup.command.internal.model.CredentialFinder;
+import com.redhat.thermostat.shared.config.CommonPaths;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CredentialFinderTest {
+    private File systemConfigDir;
+    private File userConfigDir;
+
+    private File realFile1;
+    private File realFile2;
+    private File fileToCreate1;
+    private File fileToCreate2;
+
+    private CommonPaths paths;
+
+    @Before
+    public void setUp() throws IOException {
+        systemConfigDir = Files.createTempDirectory("system-config-dir").toFile();
+        userConfigDir = Files.createTempDirectory("user-config-dir").toFile();
+        fileToCreate1 = new File(systemConfigDir.toString(), "does-not-exist");
+        fileToCreate2 = new File(userConfigDir.toString(), "does-not-exist");
+
+        realFile1 = Files.createTempFile("credentialfinder-unit-test", null).toFile();
+        realFile2 = Files.createTempFile("credentialfinder-unit-test", null).toFile();
+
+        paths = mock(CommonPaths.class);
+        when(paths.getSystemConfigurationDirectory()).thenReturn(systemConfigDir);
+        when(paths.getUserConfigurationDirectory()).thenReturn(userConfigDir);
+    }
+
+    @After
+    public void tearDown() {
+        systemConfigDir.delete();
+        userConfigDir.delete();
+        realFile1.delete();
+        realFile2.delete();
+    }
+
+    @Test
+    public void verifyFileFromUserHomeIsUsedIfSystemHomeIsNotUsable() throws IOException {
+        CredentialFinder finder = new CredentialFinder(paths) {
+            @Override
+            File getConfigurationFile(File directory, String name) {
+                if (directory == systemConfigDir) {
+                    return new File("/does-not-exist/really-it-doesn't");
+                } else if (directory == userConfigDir) {
+                    return realFile1;
+                }
+                throw new AssertionError("Unknown test case");
+            }
+        };
+
+        File config = finder.getConfiguration("web.auth");
+        assertEquals(realFile1, config);
+    }
+
+    @Test
+    public void verifyFileFromSystemHomeIsUsedIfBothAreUsable() throws IOException {
+        final File systemFile = realFile1;
+        final File userFile = realFile2;
+
+        CredentialFinder finder = new CredentialFinder(paths) {
+            @Override
+            File getConfigurationFile(File directory, String name) {
+                if (directory == systemConfigDir) {
+                    return systemFile;
+                } else if (directory == userConfigDir) {
+                    return userFile;
+                }
+                throw new AssertionError("Unknown test case");
+            }
+        };
+
+        File config = finder.getConfiguration("web.auth");
+        assertEquals(systemFile, config);
+    }
+
+    @Test
+    public void verifyFileFromSystemHomeIsUsedIfUserHomeNotUsable() throws IOException {
+        CredentialFinder finder = new CredentialFinder(paths) {
+            @Override
+            File getConfigurationFile(File directory, String name) {
+                if (directory == systemConfigDir) {
+                    return realFile1;
+                } else if (directory == userConfigDir) {
+                    return new File("/does-not-exist/really-it-doesn't");
+                }
+                throw new AssertionError("Unknown test case");
+            }
+        };
+
+        File config = finder.getConfiguration("web.auth");
+        assertEquals(realFile1, config);
+    }
+
+
+    @Test
+    public void verifyFileFromSystemHomeIsUsedIfFileDoesNotExist() throws IOException {
+        CredentialFinder finder = new CredentialFinder(paths) {
+            @Override
+            File getConfigurationFile(File directory, String name) {
+                if (directory == systemConfigDir) {
+                    return fileToCreate1;
+                } else if (directory == userConfigDir) {
+                    return fileToCreate2;
+                }
+                throw new AssertionError("Unknown test case");
+            }
+        };
+
+        File config = finder.getConfiguration("web.auth");
+        assertEquals(fileToCreate1, config);
+    }
+
+    @Test
+    public void verifyFileFromUserHomeIsUsedIfSystemHomeIsNotUsableAndFileDoesNotExist() throws IOException {
+        systemConfigDir.setReadOnly();
+
+        CredentialFinder finder = new CredentialFinder(paths) {
+            @Override
+            File getConfigurationFile(File directory, String name) {
+                if (directory == systemConfigDir) {
+                    return fileToCreate1;
+                } else if (directory == userConfigDir) {
+                    return fileToCreate2;
+                }
+                throw new AssertionError("Unknown test case");
+            }
+        };
+
+        File config = finder.getConfiguration("web.auth");
+        assertEquals(fileToCreate2, config);
+    }
+
+    @Test
+    public void verifyIsNotUsableWhenNotIsFile() throws IOException {
+        File mockFile = mock(File.class);
+        when(mockFile.exists()).thenReturn(true);
+        when(mockFile.isFile()).thenReturn(false);
+        when(mockFile.canRead()).thenReturn(true);
+        when(mockFile.canWrite()).thenReturn(true);
+        CredentialFinder finder = new CredentialFinder(paths);
+
+        assertFalse(finder.isUsable(mockFile));
+    }
+
+    @Test
+    public void verifyIsNotUsableWhenNotCanRead() throws IOException {
+        File mockFile = mock(File.class);
+        when(mockFile.exists()).thenReturn(true);
+        when(mockFile.isFile()).thenReturn(true);
+        when(mockFile.canRead()).thenReturn(false);
+        when(mockFile.canWrite()).thenReturn(true);
+        CredentialFinder finder = new CredentialFinder(paths);
+
+        assertFalse(finder.isUsable(mockFile));
+    }
+
+    @Test
+    public void verifyIsNotUsableWhenNotCanWrite() throws IOException {
+        File mockFile = mock(File.class);
+        when(mockFile.exists()).thenReturn(true);
+        when(mockFile.isFile()).thenReturn(true);
+        when(mockFile.canRead()).thenReturn(true);
+        when(mockFile.canWrite()).thenReturn(false);
+        CredentialFinder finder = new CredentialFinder(paths);
+
+        assertFalse(finder.isUsable(mockFile));
+    }
+
+    @Test
+    public void verifyIsUsableWhenFileDoesNotExistAndHasNoParent() throws IOException {
+        File fileToCreate = new File("does-not-exist");
+        CredentialFinder finder = new CredentialFinder(paths);
+
+        assertTrue(finder.isUsable(fileToCreate));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/CredentialsFileCreatorTest.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CredentialsFileCreatorTest {
+
+    private Path testRoot;
+    private File testFile;
+    private CredentialsFileCreator creator;
+    
+    @Before
+    public void setup() throws IOException {
+        creator = new CredentialsFileCreator();
+        testRoot = TestRootHelper.createTestRootDirectory(getClass().getName());
+        testFile = new File(testRoot.toFile(), "foobar.auth");
+    }
+    
+    @After
+    public void tearDown() throws IOException {
+        TestRootHelper.recursivelyRemoveTestRootDirectory(testRoot);
+    }
+    
+    @Test
+    public void testCrendentialFileCreatedIfNotExists() throws IOException {
+        File agentAuthFile = mock(File.class);
+        when(agentAuthFile.exists()).thenReturn(false);
+        when(agentAuthFile.toPath()).thenReturn(testFile.toPath());
+
+        creator.create(agentAuthFile);
+
+        assertTrue(testFile.exists());
+    }
+
+    @Test
+    public void testCrendentialFileNotCreatedIfAlreadyExists() throws IOException {
+        File agentAuthFile = mock(File.class);
+        when(agentAuthFile.exists()).thenReturn(true);
+        when(agentAuthFile.toPath()).thenReturn(testFile.toPath());
+
+        creator.create(agentAuthFile);
+
+        assertFalse(testFile.exists());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/MongodbUserSetupTest.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.Properties;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ActionNotifier;
+import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
+import com.redhat.thermostat.common.cli.Console;
+import com.redhat.thermostat.common.tools.ApplicationState;
+import com.redhat.thermostat.launcher.Launcher;
+import com.redhat.thermostat.shared.config.CommonPaths;
+
+public class MongodbUserSetupTest {
+
+    private MongodbUserSetup mongoSetup;
+    private StampFiles stampFiles;
+    private Launcher mockLauncher;
+    private CredentialFinder finder;
+    private CredentialsFileCreator fileCreator;
+    private Console console;
+    private CommonPaths paths;
+    private StructureInformation info;
+    
+    @Before
+    public void setup() {
+        paths = mock(CommonPaths.class);
+        finder = new CredentialFinder(paths);
+        console = mock(Console.class);
+        when(console.getOutput()).thenReturn(mock(PrintStream.class));
+        fileCreator = mock(CredentialsFileCreator.class);
+        stampFiles = mock(StampFiles.class);
+        info = mock(StructureInformation.class);
+        mockLauncher = mock(Launcher.class);
+        mongoSetup = new MongodbUserSetup(new UserCredsValidator(), mockLauncher, finder, fileCreator, console, paths, stampFiles, info) {
+            @Override
+            int runMongo() {
+                //instead of running mongo through ProcessBuilder
+                //we need to always return 0 for success in tests
+                return 0;
+            }
+        };
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyUsernameDisallowed() {
+        mongoSetup.createUser("", new char[] { 't' }, null);
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void nullUsernameDisallowed() {
+        mongoSetup.createUser(null, new char[] { 't' }, null);
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void nullPasswordDisallowed() {
+        mongoSetup.createUser("somebody", null, null);
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyPasswordDisallowed() {
+        mongoSetup.createUser("somebody", new char[] {}, null);
+    }
+    
+    @Test
+    public void testUnlockThermostat() throws IOException {
+        mongoSetup.unlockThermostat();
+        ArgumentCaptor<String> argCaptor = ArgumentCaptor.forClass(String.class);
+        verify(stampFiles).createSetupCompleteStamp(argCaptor.capture());
+        String contentValue = argCaptor.getValue();
+        assertTrue(contentValue.startsWith("Temporarily unlocked thermostat"));
+        assertTrue(contentValue.contains(ThermostatSetup.PROGRAM_NAME));
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testStorageStartFail() {
+        final ActionEvent<ApplicationState> mockActionEvent = mock(ActionEvent.class);
+        AbstractStateNotifyingCommand mockStorage = mock(AbstractStateNotifyingCommand.class);
+        when(mockActionEvent.getSource()).thenReturn(mockStorage);
+        when(mockStorage.getNotifier()).thenReturn(mock(ActionNotifier.class));
+        final Collection<ActionListener<ApplicationState>> listeners[] = new Collection[1];
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                listeners[0] = (Collection<ActionListener<ApplicationState>>) args[1];
+
+                when(mockActionEvent.getActionId()).thenReturn(ApplicationState.FAIL);
+
+                for (ActionListener<ApplicationState> listener : listeners[0]) {
+                    listener.actionPerformed(mockActionEvent);
+                }
+                return null;
+            }
+        }).when(mockLauncher).run(eq(MongodbUserSetup.STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
+
+        try {
+            mongoSetup.createUser("foo-user", new char[] { 't' }, "bar comment");
+            mongoSetup.commit();
+            fail("mongosetup should have failed");
+        } catch (IOException e) {
+            assertTrue(e.getMessage().contains("Thermostat storage failed to start"));
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testStorageStopFail() {
+        final ActionEvent<ApplicationState> mockActionEvent = mock(ActionEvent.class);
+        AbstractStateNotifyingCommand mockStorage = mock(AbstractStateNotifyingCommand.class);
+        when(mockActionEvent.getSource()).thenReturn(mockStorage);
+        when(mockStorage.getNotifier()).thenReturn(mock(ActionNotifier.class));
+        final Collection<ActionListener<ApplicationState>> listeners[] = new Collection[1];
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                listeners[0] = (Collection<ActionListener<ApplicationState>>) args[1];
+
+                when(mockActionEvent.getActionId()).thenReturn(ApplicationState.START);
+
+                for (ActionListener<ApplicationState> listener : listeners[0]) {
+                    listener.actionPerformed(mockActionEvent);
+                }
+                return null;
+            }
+        }).when(mockLauncher).run(eq(MongodbUserSetup.STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                listeners[0] = (Collection<ActionListener<ApplicationState>>) args[1];
+
+                when(mockActionEvent.getActionId()).thenReturn(ApplicationState.FAIL);
+
+                for (ActionListener<ApplicationState> listener : listeners[0]) {
+                    listener.actionPerformed(mockActionEvent);
+                }
+                return null;
+            }
+        }).when(mockLauncher).run(eq(MongodbUserSetup.STORAGE_STOP_ARGS), isA(Collection.class), anyBoolean());
+
+        try {
+            mongoSetup.createUser("foo-user", new char[] { 't' }, "bar comment");
+            mongoSetup.commit();
+            fail("mongosetup should have failed");
+        } catch (IOException e) {
+            assertTrue(e.getMessage().contains("Thermostat storage failed to stop"));
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testCreateMongodbUserFail() throws IOException {
+        final ActionEvent<ApplicationState> mockActionEvent = mock(ActionEvent.class);
+        AbstractStateNotifyingCommand mockStorage = mock(AbstractStateNotifyingCommand.class);
+        when(mockActionEvent.getSource()).thenReturn(mockStorage);
+        when(mockStorage.getNotifier()).thenReturn(mock(ActionNotifier.class));
+        final Collection<ActionListener<ApplicationState>> listeners[] = new Collection[1];
+        Path testRoot = TestRootHelper.createTestRootDirectory(getClass().getName());
+        when(paths.getSystemThermostatHome()).thenReturn(testRoot.toFile());
+        try {
+            mongoSetup = new MongodbUserSetup(new UserCredsValidator(), mockLauncher, finder, fileCreator, console, paths, stampFiles, info) {
+                @Override
+                int runMongo() {
+                    //return non-zero val to test failure
+                    return 1;
+                }
+            };
+    
+            doAnswer(new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) throws Throwable {
+                    Object[] args = invocation.getArguments();
+                    listeners[0] = (Collection<ActionListener<ApplicationState>>) args[1];
+    
+                    when(mockActionEvent.getActionId()).thenReturn(ApplicationState.START);
+    
+                    for (ActionListener<ApplicationState> listener : listeners[0]) {
+                        listener.actionPerformed(mockActionEvent);
+                    }
+                    return null;
+                }
+            }).when(mockLauncher).run(eq(MongodbUserSetup.STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
+    
+            try {
+                mongoSetup.createUser("foo-user", new char[] { 't' }, "bar comment");
+                mongoSetup.commit();
+                fail("mongosetup should have failed");
+            } catch (IOException e) {
+                assertTrue(e.getMessage().contains("Mongodb user setup failed"));
+            }
+            verify(stampFiles).deleteMongodbUserStamp();
+            verify(stampFiles).deleteSetupCompleteStamp();
+        } finally {
+            TestRootHelper.recursivelyRemoveTestRootDirectory(testRoot);
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSetupMongodbUser() throws IOException {
+        Path testRoot = TestRootHelper.createTestRootDirectory(getClass().getName());
+        when(paths.getSystemThermostatHome()).thenReturn(testRoot.toFile());
+        try {
+            File userDoneFile = File.createTempFile("thermostat", getClass().getName());
+            userDoneFile.deleteOnExit();
+            File setupCompleteFile = File.createTempFile("thermostat", getClass().getName());
+            setupCompleteFile.deleteOnExit();
+            when(stampFiles.getMongodbStampFile()).thenReturn(userDoneFile);
+            when(stampFiles.getSetupCompleteStampFile()).thenReturn(setupCompleteFile);
+    
+            // Fake webapp is installed.
+            when(info.isWebAppInstalled()).thenReturn(true);
+            
+            File mockWebAuthFile = File.createTempFile("thermostat", getClass().getName());
+            mockWebAuthFile.deleteOnExit();
+            finder = mock(CredentialFinder.class);
+            when(finder.getConfiguration("web.auth")).thenReturn(mockWebAuthFile);
+    
+            final ActionEvent<ApplicationState> mockActionEvent = mock(ActionEvent.class);
+            AbstractStateNotifyingCommand mockStorage = mock(AbstractStateNotifyingCommand.class);
+            when(mockActionEvent.getSource()).thenReturn(mockStorage);
+            when(mockStorage.getNotifier()).thenReturn(mock(ActionNotifier.class));
+            final Collection<ActionListener<ApplicationState>> listeners[] = new Collection[1];
+            doAnswer(new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) throws Throwable {
+                    Object[] args = invocation.getArguments();
+                    listeners[0] = (Collection<ActionListener<ApplicationState>>) args[1];
+    
+                    when(mockActionEvent.getActionId()).thenReturn(ApplicationState.START);
+    
+                    for (ActionListener<ApplicationState> listener : listeners[0]) {
+                        listener.actionPerformed(mockActionEvent);
+                    }
+                    return null;
+                }
+            }).when(mockLauncher).run(eq(MongodbUserSetup.STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
+    
+            mongoSetup = new MongodbUserSetup(new UserCredsValidator(), mockLauncher, finder, fileCreator, console, paths, stampFiles, info) {
+                @Override
+                int runMongo() {
+                    //instead of running mongo through ProcessBuilder
+                    //we need to always return 0 for success in tests
+                    return 0;
+                }
+            };
+            String username = "foo-user";
+            char[] password = new char[] { 't', 'e', 's', 't' };
+            try {
+                mongoSetup.createUser(username, password, "bar comment");
+                mongoSetup.commit();
+                // pass
+            } catch (IOException e) {
+                e.printStackTrace();
+                fail("did not expect exception");
+            }
+    
+            verify(mockLauncher, times(1)).run(eq(MongodbUserSetup.STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
+            verify(mockLauncher, times(1)).run(eq(MongodbUserSetup.STORAGE_STOP_ARGS), isA(Collection.class), anyBoolean());
+            verify(mockActionEvent, times(1)).getActionId();
+            verify(fileCreator).create(mockWebAuthFile);
+            verify(stampFiles).createMongodbUserStamp();
+            // temp unlocking calls this
+            verify(stampFiles, times(1)).createSetupCompleteStamp(any(String.class));
+    
+    
+            
+            assertTrue(mockWebAuthFile.exists());
+            Properties webauthProps = new Properties();
+            try (FileInputStream fin = new FileInputStream(mockWebAuthFile)) {
+            	webauthProps.load(fin);
+            }
+            assertEquals(username, webauthProps.getProperty("storage.username"));
+            assertEquals("test", webauthProps.getProperty("storage.password"));
+            // Passed in password array is expected to be cleared.
+            assertArrayEquals(new char[] { '\0', '\0', '\0', '\0'}, password);
+        } finally {
+            TestRootHelper.recursivelyRemoveTestRootDirectory(testRoot);
+        }
+    }
+    
+    private void assertArrayEquals(char[] expected, char[] actual) {
+        assertTrue(expected.length == actual.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals(expected[i], actual[i]);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/PropertiesWriterTest.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Properties;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PropertiesWriterTest {
+    
+    private static final String ROLES_PROPERTIES = "thermostat-roles.properties";
+    private Path testRoot;
+    private Path rolesPropertiesFile;
+    
+    @Before
+    public void setup() throws IOException {
+        testRoot = TestRootHelper.createTestRootDirectory(getClass().getName());
+        Path thermostatSysHome = testRoot.resolve("system");
+        Files.createDirectory(thermostatSysHome);
+        Path sysConfigDir = thermostatSysHome.resolve("etc");
+        Files.createDirectories(sysConfigDir);
+        rolesPropertiesFile = sysConfigDir.resolve(ROLES_PROPERTIES);
+    }
+    
+    @After
+    public void tearDown() throws IOException {
+        TestRootHelper.recursivelyRemoveTestRootDirectory(testRoot);
+    }
+
+    @Test
+    public void testPropertiesWriter() throws IOException {
+        String key = "thermostat-agent";
+        String[] roles  = new String[] {
+                UserRoles.LOGIN,
+                UserRoles.PREPARE_STATEMENT,
+                UserRoles.PURGE,
+                UserRoles.REGISTER_CATEGORY,
+        };
+        StringBuilder rolesBuilder = new StringBuilder();
+        for (int i = 0; i < roles.length - 1; i++) {
+            rolesBuilder.append(roles[i] + ", " + System.getProperty("line.separator"));
+        }
+        rolesBuilder.append(roles[roles.length - 1]);
+        String value = rolesBuilder.toString();
+
+        Properties propsToStore = new Properties();
+        propsToStore.setProperty(key, value);
+        FileOutputStream roleStream = new FileOutputStream(rolesPropertiesFile.toFile());
+        propsToStore.store(new PropertiesWriter(roleStream), null);
+
+        Properties propsToLoad = new Properties();
+        propsToLoad.load(new FileInputStream(rolesPropertiesFile.toFile()));
+        String[] loadedRoles = propsToLoad.getProperty(key).split(",\\s+");
+
+        assertTrue(Arrays.asList(roles).containsAll(Arrays.asList(loadedRoles)));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/StampFilesTest.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.shared.config.CommonPaths;
+
+public class StampFilesTest {
+    
+    private Path testRoot;
+    
+    @Before
+    public void setup() throws IOException {
+        testRoot = TestRootHelper.createTestRootDirectory(getClass().getName());
+    }
+    
+    @After
+    public void teardown() throws IOException {
+        TestRootHelper.recursivelyRemoveTestRootDirectory(testRoot);
+    }
+
+    @Test
+    public void testCreateDeleteSetupComplete() throws IOException {
+        File setupCompleteFile = getStampFileRef("setup-complete.stamp");
+        
+        CommonPaths paths = mock(CommonPaths.class);
+        when(paths.getUserPersistentDataDirectory()).thenReturn(setupCompleteFile.getParentFile());
+        StampFiles stampFiles = new StampFiles(paths);
+        
+        assertFalse(setupCompleteFile.exists());
+        stampFiles.createSetupCompleteStamp("Temporarily unlocked");
+        String fileData = new String(Files.readAllBytes(setupCompleteFile.toPath()));
+        assertTrue(setupCompleteFile.exists());
+        assertEquals("Temporarily unlocked", fileData);
+        stampFiles.createSetupCompleteStamp("Other content");
+        fileData = new String(Files.readAllBytes(setupCompleteFile.toPath()));
+        assertTrue(setupCompleteFile.exists());
+        assertEquals("Other content", fileData);
+        
+        stampFiles.deleteSetupCompleteStamp();
+        assertFalse(setupCompleteFile.exists());
+    }
+    
+    @Test
+    public void testCreateDeleteMongodbUserStamp() throws IOException {
+        File mongodbUserDoneFile = getStampFileRef("mongodb-user-done.stamp");
+        
+        CommonPaths paths = mock(CommonPaths.class);
+        when(paths.getUserPersistentDataDirectory()).thenReturn(mongodbUserDoneFile.getParentFile());
+        StampFiles stampFiles = new StampFiles(paths);
+        
+        assertFalse(mongodbUserDoneFile.exists());
+        stampFiles.createMongodbUserStamp();
+        assertTrue(mongodbUserDoneFile.exists());
+        
+        stampFiles.deleteMongodbUserStamp();
+        assertFalse(mongodbUserDoneFile.exists());
+    }
+    
+    @Test
+    public void deleteNonExistingMongodbCompleteStamp() throws IOException {
+        File mongodbUserDoneFile = getStampFileRef("mongodb-user-done.stamp");
+        CommonPaths paths = mock(CommonPaths.class);
+        when(paths.getUserPersistentDataDirectory()).thenReturn(mongodbUserDoneFile.getParentFile());
+        StampFiles stampFiles = new StampFiles(paths);
+        
+        assertFalse(mongodbUserDoneFile.exists());
+        try {
+            stampFiles.deleteMongodbUserStamp();
+            // pass
+        } catch (Exception e) {
+            fail("Did not expect exception for deleting non-existent file.");
+        }
+        assertFalse(mongodbUserDoneFile.exists());
+    }
+    
+    @Test
+    public void deleteNonExistingSetupCompleteStamp() throws IOException {
+        File setupCompleteFile = getStampFileRef("setup-complete.stamp");
+        CommonPaths paths = mock(CommonPaths.class);
+        when(paths.getUserPersistentDataDirectory()).thenReturn(setupCompleteFile.getParentFile());
+        StampFiles stampFiles = new StampFiles(paths);
+        
+        assertFalse(setupCompleteFile.exists());
+        try {
+            stampFiles.deleteSetupCompleteStamp();
+            // pass
+        } catch (Exception e) {
+            fail("Did not expect exception for deleting non-existent file.");
+        }
+        assertFalse(setupCompleteFile.exists());
+    }
+    
+    private File getStampFileRef(String name) throws IOException {
+        Path thermostatUserHome = testRoot.resolve("user");
+        Path thermostatUserData = thermostatUserHome.resolve("data");
+        File file = new File(thermostatUserData.toFile(), name);
+        Files.createDirectories(thermostatUserData);
+        return file;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/StructureInformationTest.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.redhat.thermostat.shared.config.CommonPaths;
+
+public class StructureInformationTest {
+    
+    private Path testRoot;
+    private Path thermostatSysHome;
+    private StructureInformation structureInfo;
+    
+    @Before
+    public void setup() throws IOException {
+        testRoot = TestRootHelper.createTestRootDirectory(getClass().getName());
+
+        thermostatSysHome = testRoot.resolve("system");
+        Files.createDirectory(thermostatSysHome);
+        CommonPaths mockPaths = mock(CommonPaths.class);
+        when(mockPaths.getSystemThermostatHome()).thenReturn(thermostatSysHome.toFile());
+        structureInfo = new StructureInformation(mockPaths);
+    }
+    
+    @After
+    public void teardown() throws IOException {
+        TestRootHelper.recursivelyRemoveTestRootDirectory(testRoot);
+    }
+
+    @Test
+    public void testWebAppInstalledSuccess() throws IOException {
+        Path webAppPath = thermostatSysHome.resolve("webapp");
+        Files.createDirectories(webAppPath);
+        assertTrue(structureInfo.isWebAppInstalled());
+    }
+
+    @Test
+    public void testWebAppInstalledFail() throws IOException {
+        //Call isWebAppInstalled() without creating
+        //a THERMOSTAT_SYS_HOME/webapp directory
+        assertFalse(structureInfo.isWebAppInstalled());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/TestRootHelper.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+class TestRootHelper {
+
+    static Path createTestRootDirectory(String name) throws IOException {
+        Path testRoot = Files.createTempDirectory("thermostat-" + name);
+        return testRoot;
+    }
+    
+    static void recursivelyRemoveTestRootDirectory(Path dir) throws IOException {
+        Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                Files.delete(file);
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                if (exc == null) {
+                    Files.delete(dir);
+                    return FileVisitResult.CONTINUE;
+                } else {
+                    throw exc;
+                }
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/ThermostatSetupTest.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.shared.config.CommonPaths;
+
+public class ThermostatSetupTest {
+
+    private ThermostatUserSetup userSetup;
+    private MongodbUserSetup mongoUserSetup;
+    
+    @Before
+    public void setup() {
+        userSetup = mock(ThermostatUserSetup.class);
+        mongoUserSetup = mock(MongodbUserSetup.class);
+    }
+    
+    @Test
+    public void testIsWebAppInstalledDelegates() {
+        StructureInformation structureInfo = mock(StructureInformation.class);
+        ThermostatSetup setup = new ThermostatSetup(userSetup, mongoUserSetup, structureInfo, mock(CommonPaths.class), mock(CredentialsFileCreator.class));
+        when(structureInfo.isWebAppInstalled()).thenReturn(true);
+        assertTrue(setup.isWebAppInstalled());
+        verify(structureInfo).isWebAppInstalled();
+    }
+    
+    @Test
+    public void testCreateAgentUser() {
+        ThermostatSetup setup = new ThermostatSetup(userSetup, mongoUserSetup, mock(StructureInformation.class), mock(CommonPaths.class), mock(CredentialsFileCreator.class));
+        setup.createAgentUser("foo-agent", new char[] { 't' });
+        verify(userSetup).createRecursiveRole(eq("thermostat-agent"), argThat(new RoleMatcher(UserRoles.AGENT_ROLES)), any(String.class));
+        verify(userSetup).assignRolesToUser(eq("foo-agent"), argThat(new RoleMatcher(new String[] { "thermostat-agent", UserRoles.GRANT_FILES_WRITE_ALL })), any(String.class));
+    }
+    
+    @Test
+    public void testCreateClientAdminUser() {
+        ThermostatSetup setup = new ThermostatSetup(userSetup, mongoUserSetup, mock(StructureInformation.class), mock(CommonPaths.class), mock(CredentialsFileCreator.class));
+        setup.createClientAdminUser("foo-client", new char[] { 't' });
+        verify(userSetup).createRecursiveRole(eq("thermostat-client"), argThat(new RoleMatcher(UserRoles.CLIENT_ROLES)), any(String.class));
+        verify(userSetup).createRecursiveRole(eq("thermostat-cmdc"), argThat(new RoleMatcher(UserRoles.CMD_CHANNEL_GRANT_ALL_ACTIONS)), any(String.class));
+        verify(userSetup).createRecursiveRole(eq("thermostat-admin-read-all"), argThat(new RoleMatcher(UserRoles.ADMIN_READALL)), any(String.class));
+        String[] clientAllRoles = new String[] { "thermostat-client", "thermostat-cmdc", "thermostat-admin-read-all", UserRoles.PURGE };
+        verify(userSetup).assignRolesToUser(eq("foo-client"), argThat(new RoleMatcher(clientAllRoles)), any(String.class));
+    }
+    
+    @Test
+    public void flushCreatesAgentAuthFile() throws IOException {
+        CommonPaths paths = mock(CommonPaths.class);
+        File mockAgentAuthFile = File.createTempFile("thermostat-test-", getClass().getName());
+        try {
+            when(paths.getUserAgentAuthConfigFile()).thenReturn(mockAgentAuthFile);
+            ThermostatSetup setup = new ThermostatSetup(userSetup, mongoUserSetup, mock(StructureInformation.class), paths, mock(CredentialsFileCreator.class));
+            List<String> contents = Files.readAllLines(mockAgentAuthFile.toPath(), Charset.forName("UTF-8"));
+            assertEquals(0, contents.size());
+            setup.createAgentUser("damian", new char[] { 't', 'e', 's', 't' });
+            setup.commit();
+            verify(userSetup).commit();
+            verify(mongoUserSetup).commit();
+            contents = Files.readAllLines(mockAgentAuthFile.toPath(), Charset.forName("UTF-8"));
+            assertTrue("username and password must be present", contents.size() > 2);
+            assertTrue("username=damian expected to be found in agent.auth file", contents.contains("username=damian"));
+            assertTrue("password=test expected to be found in agent.auth file", contents.contains("password=test"));
+        } finally {
+            Files.delete(mockAgentAuthFile.toPath());
+        }
+    }
+    
+    private class RoleMatcher extends BaseMatcher<String[]> {
+        
+        final String[] expected;
+        private RoleMatcher(String[] expected) {
+            this.expected = expected;
+        }
+
+        @Override
+        public void describeTo(Description arg0) {
+            arg0.appendText(Arrays.asList(expected).toString());
+        }
+
+        @Override
+        public boolean matches(Object arg0) {
+            if (arg0.getClass() != String[].class) {
+                return false;
+            }
+            String[] other = (String[])arg0;
+            if (other.length != expected.length) {
+                return false;
+            }
+            boolean match = true;
+            for (int i = 0; i < expected.length; i++) {
+                match = match && Objects.equals(expected[i], other[i]);
+            }
+            return match;
+        }
+        
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/ThermostatUserSetupTest.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Scanner;
+import java.util.Set;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.setup.command.internal.model.ThermostatUserSetup.CommentedCredsPropertyValue;
+import com.redhat.thermostat.setup.command.internal.model.ThermostatUserSetup.CommentedRolePropertyValue;
+
+public class ThermostatUserSetupTest {
+    
+    private ThermostatUserSetup tSetup;
+    
+    @Before
+    public void setup() throws IOException {
+        tSetup = new ThermostatUserSetup(mock(UserPropertiesFinder.class), mock(UserCredsValidator.class), mock(CredentialsFileCreator.class), mock(StampFiles.class));
+    }
+    
+    @Test
+    public void canCreateCommentedUser() {
+        char[] fooUserPwd = new char[] { 't', 'e', 's', 't'};
+        tSetup.createUser("foo-user", fooUserPwd, "some-comment");
+        char[] otherBarPwd = new char[] { 'o', 't', 'h', 'e', 'r'};
+        tSetup.createUser("other-bar", otherBarPwd, "comment for bar");
+        char[] thirdUserPwd = new char[] { 'n', 'o', 'm', 'a', 't', 't', 'e', 'r' };
+        tSetup.createUser("third-user", thirdUserPwd, null);
+        Map<String, CommentedCredsPropertyValue> props = tSetup.buildUserProperties();
+        
+        CommentedCredsPropertyValue fooUser = props.get("foo-user");
+        assertSame(fooUserPwd, fooUser.getValue());
+        assertEquals("some-comment", fooUser.getComment());
+        
+        CommentedCredsPropertyValue otherBar = props.get("other-bar");
+        assertSame(otherBarPwd, otherBar.getValue());
+        assertEquals("comment for bar", otherBar.getComment());
+        
+        CommentedCredsPropertyValue thirdValue = props.get("third-user");
+        assertSame(thirdUserPwd, thirdValue.getValue());
+        assertNull(thirdValue.getComment());
+        
+        assertNull(props.get("i-wasn't-created-user"));
+    }
+    
+    @Test
+    public void canCreateCommentedRecursiveRole() {
+        String[] primitives = new String[] {
+                "role1",
+                "role2"
+        };
+        tSetup.createRecursiveRole("recursive-role", primitives, "comment for recursive role");
+        Map<String, CommentedRolePropertyValue> roles = tSetup.buildRoleProperties();
+        CommentedRolePropertyValue recRoleValue = roles.get("recursive-role");
+        assertNotNull(recRoleValue);
+        String expectedValue = "role1, " + System.lineSeparator() +
+                               "role2";
+        assertEquals(expectedValue, recRoleValue.getValue());
+        assertEquals("comment for recursive role", recRoleValue.getComment());
+    }
+    
+    @Test
+    public void canAssignRolesToUser() {
+        String[] primitives = new String[] {
+                "role1",
+                "role2"
+        };
+        tSetup.assignRolesToUser("some-user", primitives, null);
+        Map<String, CommentedRolePropertyValue> roleProps = tSetup.buildRoleProperties();
+        CommentedRolePropertyValue val = roleProps.get("some-user");
+        assertNotNull(val);
+        assertNull(val.getComment());
+        String expectedRolesStr = "role1, " + System.lineSeparator() + "role2";
+        assertEquals(expectedRolesStr, val.getValue());
+    }
+    
+    @Test
+    public void testRoleSetToString() {
+        String[] roleSet = new String[] {
+                "foo-one",
+                "foo-two"
+        };
+        String expectedString = "foo-one, " + System.lineSeparator() + "foo-two";
+        assertEquals(expectedString, tSetup.roleSetToString(roleSet));
+        assertTrue(tSetup.roleSetToString(new String[] {}).isEmpty());
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void assigningRolesValidatesNullValue() {
+        tSetup.assignRolesToUser("something", null, null);
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void assigningRolesValidatesEmptyRoles() {
+        tSetup.assignRolesToUser("something", new String[] {}, null);
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void creatingRecursiveRolesValidatesNullValue() {
+        tSetup.createRecursiveRole("something", null, null);
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void creatingRecursiveRolesValidatesEmptyRoles() {
+        tSetup.createRecursiveRole("something", new String[] {}, null);
+    }
+    
+    @Test
+    public void canWriteRoles() throws IOException {
+        Map<String, CommentedRolePropertyValue> roleProps = new HashMap<>();
+        String roleValue = "thermostat-login, \nthermostat-test";
+        CommentedRolePropertyValue thermostatAgent = new CommentedRolePropertyValue(roleValue, "some comment");
+        roleProps.put("thermostat-agent", thermostatAgent);
+        CommentedRolePropertyValue agentUser = new CommentedRolePropertyValue("thermostat-agent", "agent user");
+        roleProps.put("foo-agent-user", agentUser);
+        
+        UserPropertiesFinder propsFinder = mock(UserPropertiesFinder.class);
+        File tmpRolesPropsFile = File.createTempFile("thermostat", getClass().getName());
+        try {
+            when(propsFinder.getRolesProperties()).thenReturn(tmpRolesPropsFile);
+            ThermostatUserSetup userSetup = new ThermostatUserSetup(propsFinder, mock(UserCredsValidator.class), mock(CredentialsFileCreator.class), mock(StampFiles.class)) {
+                
+                @Override
+                Properties readPropsFromFile(File propsFile) {
+                    // simulate empty existing properties
+                    return new Properties();
+                }
+            };
+            userSetup.writeRoles(roleProps);
+            List<String> expectedList = new ArrayList<>();
+            expectedList.add("#some comment");
+            expectedList.add("thermostat-agent=thermostat-login, \\");
+            expectedList.add("foo-agent-user=thermostat-agent");
+            expectedList.add("#agent user");
+            verifyFileContainsLines(tmpRolesPropsFile, expectedList);
+        } finally {
+            Files.delete(tmpRolesPropsFile.toPath());
+        }
+    }
+    
+    @Test
+    public void canWriteUsers() throws IOException {
+        Map<String, CommentedCredsPropertyValue> userCreds = new HashMap<>();
+        CommentedCredsPropertyValue user1Val = new CommentedCredsPropertyValue(new char[] { 't' }, "user1");
+        userCreds.put("testuser1", user1Val);
+        CommentedCredsPropertyValue user2Val = new CommentedCredsPropertyValue(new char[] { 'b', 'a', 'r' }, "speedy");
+        userCreds.put("speedy-user", user2Val);
+        
+        UserPropertiesFinder propsFinder = mock(UserPropertiesFinder.class);
+        File tmpUsersPropsFile = File.createTempFile("thermostat", getClass().getName());
+        try {
+            when(propsFinder.getUserProperties()).thenReturn(tmpUsersPropsFile);
+            ThermostatUserSetup userSetup = new ThermostatUserSetup(propsFinder, mock(UserCredsValidator.class), mock(CredentialsFileCreator.class), mock(StampFiles.class)) {
+                
+                @Override
+                Properties readPropsFromFile(File propsFile) {
+                    return new Properties();
+                }
+            };
+            userSetup.writeUsers(userCreds);
+            List<String> expectedList = new ArrayList<>();
+            expectedList.add("#user1");
+            expectedList.add("testuser1=t");
+            expectedList.add("speedy-user=bar");
+            expectedList.add("#speedy");
+            verifyFileContainsLines(tmpUsersPropsFile, expectedList);
+        } finally {
+            Files.delete(tmpUsersPropsFile.toPath());
+        }
+    }
+    
+    @Test
+    public void writingUsersDoesnotProduceDuplicates() throws IOException {
+        Map<String, CommentedCredsPropertyValue> userCreds = new HashMap<>();
+        CommentedCredsPropertyValue user1Val = new CommentedCredsPropertyValue(new char[] { 't' }, "user1");
+        userCreds.put("testuser1", user1Val);
+        CommentedCredsPropertyValue user2Val = new CommentedCredsPropertyValue(new char[] { 'b', 'a', 'r' }, "speedy");
+        userCreds.put("speedy-user", user2Val);
+        
+        UserPropertiesFinder propsFinder = mock(UserPropertiesFinder.class);
+        File tmpUsersPropsFile = File.createTempFile("thermostat", getClass().getName());
+        try {
+            when(propsFinder.getUserProperties()).thenReturn(tmpUsersPropsFile);
+            ThermostatUserSetup userSetup = new ThermostatUserSetup(propsFinder, mock(UserCredsValidator.class), mock(CredentialsFileCreator.class), mock(StampFiles.class)) {
+                
+                @Override
+                Properties readPropsFromFile(File propsFile) {
+                    return new Properties();
+                }
+            };
+            userSetup.writeUsers(userCreds);
+            userSetup = new ThermostatUserSetup(propsFinder, mock(UserCredsValidator.class), mock(CredentialsFileCreator.class), mock(StampFiles.class));
+            // write same users again
+            userSetup.writeUsers(userCreds);
+            List<String> expectedLines = new ArrayList<>();
+            expectedLines.add("testuser1=t");
+            expectedLines.add("speedy-user=bar");
+            verifyFileContainsLines(tmpUsersPropsFile, expectedLines);
+            assertEquals("expected testuser1=t only once in file.", 1, getNumOccurance(expectedLines.get(0), getLinesAsList(tmpUsersPropsFile)));
+            assertEquals("expected speedy-user=bar only once in file.", 1, getNumOccurance(expectedLines.get(1), getLinesAsList(tmpUsersPropsFile)));
+        } finally {
+            Files.delete(tmpUsersPropsFile.toPath());
+        }
+    }
+    
+    @Test
+    public void writingRolesDoesnotProduceDuplicates() throws IOException {
+        Map<String, CommentedRolePropertyValue> roleProps = new HashMap<>();
+        String roleValue = "thermostat-login, \nthermostat-test";
+        CommentedRolePropertyValue thermostatAgent = new CommentedRolePropertyValue(roleValue, "some comment");
+        roleProps.put("thermostat-agent", thermostatAgent);
+        CommentedRolePropertyValue agentUser = new CommentedRolePropertyValue("thermostat-agent", "agent user");
+        roleProps.put("foo-agent-user", agentUser);
+        
+        UserPropertiesFinder propsFinder = mock(UserPropertiesFinder.class);
+        File tmpRolesPropsFile = File.createTempFile("thermostat", getClass().getName());
+        try {
+            when(propsFinder.getRolesProperties()).thenReturn(tmpRolesPropsFile);
+            ThermostatUserSetup userSetup = new ThermostatUserSetup(propsFinder, mock(UserCredsValidator.class), mock(CredentialsFileCreator.class), mock(StampFiles.class)) {
+                
+                @Override
+                Properties readPropsFromFile(File propsFile) {
+                    // simulate empty existing properties
+                    return new Properties();
+                }
+            };
+            userSetup.writeRoles(roleProps);
+            userSetup = new ThermostatUserSetup(propsFinder, mock(UserCredsValidator.class), mock(CredentialsFileCreator.class), mock(StampFiles.class));
+            // write users again
+            userSetup.writeRoles(roleProps);
+            List<String> expectedList = new ArrayList<>();
+            expectedList.add("thermostat-agent=thermostat-login, \\");
+            expectedList.add("foo-agent-user=thermostat-agent");
+            verifyFileContainsLines(tmpRolesPropsFile, expectedList);
+            assertEquals("expected 'thermostat-agent=thermostat-login, \\' only once in file.", 1, getNumOccurance(expectedList.get(0), getLinesAsList(tmpRolesPropsFile)));
+            assertEquals("expected 'foo-agent-user=thermostat-agent' only once in file.", 1, getNumOccurance(expectedList.get(1), getLinesAsList(tmpRolesPropsFile)));
+        } finally {
+            Files.delete(tmpRolesPropsFile.toPath());
+        }
+    }
+
+    private int getNumOccurance(String itemToSearch, List<String> list) {
+        int count = 0;
+        for (String line: list) {
+            if (line.equals(itemToSearch)) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    private void verifyFileContainsLines(File file, List<String> expectedList) throws FileNotFoundException, IOException {
+        Set<String> fileContents = getLinesAsSet(file);
+        for (String line: expectedList) {
+            assertTrue("Expected " + fileContents + " to contain line ' " + line + "'.", fileContents.contains(line));
+        }
+    }
+
+    private Set<String> getLinesAsSet(File file) throws IOException,
+            FileNotFoundException {
+        Set<String> fileContents = new HashSet<>();
+        try (FileInputStream fin = new FileInputStream(file);
+             Scanner fileScanner = new Scanner(fin)) {
+            while (fileScanner.hasNextLine()) {
+                String line = fileScanner.nextLine();
+                fileContents.add(line);
+            }
+        }
+        return fileContents;
+    }
+    
+    private List<String> getLinesAsList(File file) throws IOException {
+        return Files.readAllLines(file.toPath(), Charset.forName("UTF-8"));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/UserCredsValidatorTest.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class UserCredsValidatorTest {
+
+    private UserCredsValidator validator;
+    
+    @Before
+    public void setup() {
+        validator = new UserCredsValidator();
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void testValidateUsernameNull() {
+        validator.validateUsername(null);
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void testValidateUsernameEmpty() {
+        validator.validateUsername("");
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void testValidatePasswordNull() {
+        validator.validatePassword(null);
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void testValidatePasswordEmpty() {
+        validator.validatePassword(new char[] {});
+    }
+    
+    @Test
+    public void validateUsernamePassesOnValid() {
+        try {
+            validator.validateUsername("some-non-empty-username");
+        } catch (IllegalArgumentException e) {
+            fail("username expected to be valid");
+        }
+    }
+    
+    @Test
+    public void validatePasswordPassesOnValid() {
+        try {
+            validator.validatePassword(new char[] { 't', 'e', 's', 't' });
+        } catch (IllegalArgumentException e) {
+            fail("password expected to be valid");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup-command/command/src/test/java/com/redhat/thermostat/setup/command/internal/model/UserPropertiesFinderTest.java	Wed Sep 02 15:27:53 2015 +0200
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.model;
+
+import java.io.File;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class UserPropertiesFinderTest {
+
+    private UserPropertiesFinder propFinder;
+    private File userPropFile = mock(File.class);
+    private File rolesPropFile = mock(File.class);
+    
+    @Before
+    public void setup() {
+        CredentialFinder finder = mock(CredentialFinder.class);
+        when(finder.getConfiguration("thermostat-users.properties")).thenReturn(userPropFile);
+        when(finder.getConfiguration("thermostat-roles.properties")).thenReturn(rolesPropFile);
+        propFinder = new UserPropertiesFinder(finder);
+    }
+
+    @Test
+    public void userPropertiesDelegatesToCredsFinder() {
+        File result = propFinder.getUserProperties();
+        assertSame(result, userPropFile);
+    }
+    
+    @Test
+    public void rolesPropertiesDelegatesToCredsFinder() {
+        File result = propFinder.getRolesProperties();
+        assertSame(result, rolesPropFile);
+    }
+}