You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by tu...@apache.org on 2015/11/03 12:26:35 UTC

[73/79] incubator-geode git commit: Integrated Security related automated test using selenium. Test pulse authentication and access to data browser through selenium Review : https://reviews.apache.org/r/39646

Integrated Security related automated test using selenium. Test pulse authentication and access to data browser through selenium
Review : https://reviews.apache.org/r/39646


Project: http://git-wip-us.apache.org/repos/asf/incubator-geode/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-geode/commit/87c83da2
Tree: http://git-wip-us.apache.org/repos/asf/incubator-geode/tree/87c83da2
Diff: http://git-wip-us.apache.org/repos/asf/incubator-geode/diff/87c83da2

Branch: refs/heads/feature/GEODE-17
Commit: 87c83da23a4fd281d9ca0fda2cd61e57d7cfef52
Parents: 813d24c
Author: tushark <tu...@apache.org>
Authored: Mon Nov 2 18:32:11 2015 +0530
Committer: tushark <tu...@apache.org>
Committed: Tue Nov 3 16:54:35 2015 +0530

----------------------------------------------------------------------
 .../pulse/internal/security/LogoutHandler.java  |   6 +-
 .../tools/pulse/tests/AccessControl.java        |  45 ++++
 .../tools/pulse/tests/AccessControlMBean.java   |   7 +
 .../tools/pulse/tests/IntegrateSecUITest.java   | 245 +++++++++++++++++++
 .../pulse/tests/PropsBackedAuthenticator.java   |  48 ++++
 .../gemfire/tools/pulse/tests/PulseTests.java   |   2 +-
 .../gemfire/tools/pulse/tests/Server.java       |  86 ++++---
 pulse/src/test/resources/test.properties        |  12 +
 8 files changed, 419 insertions(+), 32 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/87c83da2/pulse/src/main/java/com/vmware/gemfire/tools/pulse/internal/security/LogoutHandler.java
----------------------------------------------------------------------
diff --git a/pulse/src/main/java/com/vmware/gemfire/tools/pulse/internal/security/LogoutHandler.java b/pulse/src/main/java/com/vmware/gemfire/tools/pulse/internal/security/LogoutHandler.java
index 78d4703..b6187bc 100644
--- a/pulse/src/main/java/com/vmware/gemfire/tools/pulse/internal/security/LogoutHandler.java
+++ b/pulse/src/main/java/com/vmware/gemfire/tools/pulse/internal/security/LogoutHandler.java
@@ -30,8 +30,10 @@ public class LogoutHandler extends SimpleUrlLogoutSuccessHandler implements Logo
     LOGGER.fine("Invoked #LogoutHandler ...");
     if (Repository.get().isUseGemFireCredentials()) {
       GemFireAuthentication gemauthentication = (GemFireAuthentication) authentication;
-      gemauthentication.getJmxc().close();
-      LOGGER.info("#LogoutHandler : Closing GemFireAuthentication JMX Connection...");
+      if(gemauthentication!=null) {
+        gemauthentication.getJmxc().close();
+        LOGGER.fine("#LogoutHandler : Closing GemFireAuthentication JMX Connection...");
+      }
     }
     super.onLogoutSuccess(request, response, authentication);
   }

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/87c83da2/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/AccessControl.java
----------------------------------------------------------------------
diff --git a/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/AccessControl.java b/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/AccessControl.java
new file mode 100644
index 0000000..ee0d43b
--- /dev/null
+++ b/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/AccessControl.java
@@ -0,0 +1,45 @@
+package com.vmware.gemfire.tools.pulse.tests;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.Principal;
+import java.util.Set;
+
+import javax.management.remote.JMXPrincipal;
+import javax.security.auth.Subject;
+
+/**
+ * 
+ * @author tushark
+ *
+ */
+public class AccessControl extends JMXBaseBean implements AccessControlMBean {
+
+  public static final String OBJECT_NAME_ACCESSCONTROL = "GemFire:service=AccessControl,type=Distributed";
+
+  @Override
+  public boolean authorize(String role) {
+    AccessControlContext acc = AccessController.getContext();
+    Subject subject = Subject.getSubject(acc);
+    Set<JMXPrincipal> principals = subject.getPrincipals(JMXPrincipal.class);    
+    if (principals == null || principals.isEmpty()) {
+      throw new SecurityException("Access denied");
+    }
+    
+    Principal principal = principals.iterator().next();    
+    String roleArray[] = getStringArray(principal.getName());
+    if(roleArray!=null) {
+      for(String roleStr:roleArray) {
+        if(roleStr.equals(role))
+          return true;
+      }
+    }    
+    return false;
+  }
+
+  @Override
+  protected String getKey(String propName) {    
+    return "users." + propName + "." + "roles";
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/87c83da2/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/AccessControlMBean.java
----------------------------------------------------------------------
diff --git a/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/AccessControlMBean.java b/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/AccessControlMBean.java
new file mode 100644
index 0000000..70423e9
--- /dev/null
+++ b/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/AccessControlMBean.java
@@ -0,0 +1,7 @@
+package com.vmware.gemfire.tools.pulse.tests;
+
+public interface AccessControlMBean {
+
+  public boolean authorize(String role);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/87c83da2/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/IntegrateSecUITest.java
----------------------------------------------------------------------
diff --git a/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/IntegrateSecUITest.java b/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/IntegrateSecUITest.java
new file mode 100644
index 0000000..cc6e5b6
--- /dev/null
+++ b/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/IntegrateSecUITest.java
@@ -0,0 +1,245 @@
+package com.vmware.gemfire.tools.pulse.tests;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.management.JMException;
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+
+import static org.junit.Assert.*;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.startup.Tomcat;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.firefox.FirefoxDriver;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+public class IntegrateSecUITest {
+  
+  private final static String jmxPropertiesFile = System.getProperty("pulse.propfile");
+  private static String path = System.getProperty("pulse.war");
+  private static Tomcat tomcat = null;
+  private static Server server = null;
+  private static String pulseURL = null;
+  private static String logoutURL = null;
+  private static String loginURL = null;
+  public static WebDriver driver;
+
+  @BeforeClass
+  public static void setUpBeforeClass() throws Exception {
+    try {      
+      //Enable Integrated Security Profile
+      System.setProperty("spring.profiles.active", "pulse.authentication.gemfire");
+      //assumes jmx port is 1099 in pulse war file
+      server = Server.createServer(1099, jmxPropertiesFile, true);
+      String host = "localhost";// InetAddress.getLocalHost().getHostAddress();
+      int port = 8080;
+      String context = "/pulse";      
+      tomcat = TomcatHelper.startTomcat(host, port, context, path);
+      pulseURL = "http://" + host + ":" + port + context;
+      logoutURL = "http://" + host + ":" + port  + context + "/pulse/clusterLogout";
+      loginURL = "http://" + host + ":" + port  + context + "/Login.html";
+      Thread.sleep(5000); // wait till tomcat settles down
+    } catch (FileNotFoundException e) {
+      e.printStackTrace();
+      fail("Error " + e.getMessage());
+    } catch (IOException e) {
+      e.printStackTrace();
+      fail("Error " + e.getMessage());
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail("Error " + e.getMessage());
+    }
+
+    driver = new FirefoxDriver();
+    driver.manage().window().maximize();
+    driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS);
+    driver.get(pulseURL);  
+  }
+  
+  /**
+   * This test only tests test extensions (ie. properties file based testbed) written for integrate security
+   *    1. PropsBackedAuthenticator
+   *    2. AccessControlMbean
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void testServerAuthentication() throws IOException {
+    JMXConnector jmxc = attemptConnect("dataRead", "dataRead" , true);
+    jmxc.close();
+    attemptConnect("dataRead", "dataRead12321" , false);
+  }
+  
+  @Test
+  public void testServerAuthorization() throws IOException, JMException {        
+    try {
+      JMXConnector cc = attemptConnect("dataRead", "dataRead" , true);
+      testLevel(cc,"PULSE_DASHBOARD", true);
+      testLevel(cc,"PULSE_DATABROWSER", false);
+      cc.close();
+      
+      cc = attemptConnect("dataWrite", "dataWrite" , true);
+      testLevel(cc,"PULSE_DASHBOARD", true);
+      testLevel(cc,"PULSE_DATABROWSER", true);
+      cc.close();
+      
+      cc = attemptConnect("admin", "admin" , true);
+      testLevel(cc,"PULSE_DASHBOARD", true);
+      testLevel(cc,"PULSE_DATABROWSER", true);
+      cc.close();
+      
+    } catch (SecurityException e) {
+      fail("Authentication failed " + e.getMessage());
+    }
+  }
+  
+  /**
+   * Test pulse authentication through pulse login page and clusterLogout  
+   */
+  @Test
+  public void testPulseAuthentication() throws InterruptedException {
+    login("dataRead", "dataRead", true, true);
+    login("dataRead", "dataRead1234", true, false);
+    logout();
+  }
+
+  /**
+   * Test pulse authorization through pulse data browser page  
+   */
+  @Test
+  public void testPulseAuthorization() throws InterruptedException {
+    login("dataWrite", "dataWrite", false,true);
+    navigateToDataBrowerPage(true);    
+    login("dataRead", "dataRead", true,true);
+    navigateToDataBrowerPage(false);
+  }
+  
+  private JMXConnector attemptConnect(String user, String password, boolean expectSuccess) throws IOException {
+    String[] creds = { user, password };
+    Map<String, Object> env = new HashMap<String, Object>();
+    env.put(JMXConnector.CREDENTIALS, creds);
+    try {
+      JMXConnector cc = JMXConnectorFactory.connect(getURL(), env);
+      MBeanServerConnection mbsc = cc.getMBeanServerConnection();
+      if(!expectSuccess)
+        fail("Expected Authentication to fail");
+      return cc;
+    } catch (SecurityException e) {
+      if(expectSuccess)
+        fail("Authentication failed " + e.getMessage());
+    }
+    return null;
+  }
+  
+  private JMXServiceURL getURL() throws IOException {    
+    return new JMXServiceURL("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi");
+  }
+  
+  private void testLevel(JMXConnector jmxc, String role, boolean expectTrue) throws IOException, JMException {
+    MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();      
+    ObjectName accObjName = new ObjectName(AccessControl.OBJECT_NAME_ACCESSCONTROL);
+    boolean hasAccess = (Boolean)mbsc.invoke(accObjName, "authorize", 
+        new Object[]{role}, new String[] {String.class.getName()});
+    if(expectTrue) {
+      assertTrue(hasAccess);
+    }
+    else {
+      if(hasAccess)
+        fail("Expected role "+ role + " rejection but user return true");
+    }     
+  }
+  
+  private void navigateToDataBrowerPage(boolean expectSuccess) {
+    WebElement element = driver.findElement(By.linkText("Data Browser"));
+    element.click();
+    if(expectSuccess) {
+      WebElement dbHeader = (new WebDriverWait(driver, 10))
+          .until(new ExpectedCondition<WebElement>() {
+            @Override
+            public WebElement apply(WebDriver d) {
+              return d.findElement(By.xpath("//*[@id=\"canvasWidth\"]/div[4]/div[1]/div[2]/label"));
+            }
+          });
+      assertNotNull(dbHeader);      
+    }
+  }
+
+  private void logout() {
+    driver.get(logoutURL); 
+    validateLoginPage();
+  }
+  
+  private void validateSuccessfulLogin() {
+    WebElement userNameOnPulsePage = (new WebDriverWait(driver, 10))
+        .until(new ExpectedCondition<WebElement>() {
+          @Override
+          public WebElement apply(WebDriver d) {
+            return d.findElement(By.id("userName"));
+          }
+        });
+    assertNotNull(userNameOnPulsePage);
+  }
+  
+  private void validateLoginPage() {
+    WebElement userNameOnPulseLoginPage = (new WebDriverWait(driver, 10))
+        .until(new ExpectedCondition<WebElement>() {
+          @Override
+          public WebElement apply(WebDriver d) {
+            return d.findElement(By.id("user_name"));
+          }
+        });
+    assertNotNull(userNameOnPulseLoginPage);
+  }
+  
+  private void login(String userName, String password, boolean logoutFirst, boolean expectSuccess) throws InterruptedException {
+    if(logoutFirst) {
+      logout();
+    }
+    WebElement userNameElement = driver.findElement(By.id("user_name"));
+    WebElement passwordElement = driver.findElement(By.id("user_password"));
+    userNameElement.sendKeys(userName);
+    passwordElement.sendKeys(password);
+    passwordElement.submit();
+    if(expectSuccess) {
+      validateSuccessfulLogin();
+    } else {
+      //We expect login to be unsucceesful so it should go back to Login.html
+      validateLoginPage();
+    }    
+  }
+  
+  
+  @AfterClass
+  public static void tearDownAfterClass() throws Exception {
+    if(driver!=null)
+      driver.close();
+    try {
+      if (tomcat != null) {
+        tomcat.stop();
+        tomcat.destroy();
+      }
+      System.out.println("Tomcat Stopped");
+      if (server != null) {
+        server.stop();
+      }
+      System.out.println("Server Stopped");
+    } catch (LifecycleException e) {
+      e.printStackTrace();
+    }
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/87c83da2/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/PropsBackedAuthenticator.java
----------------------------------------------------------------------
diff --git a/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/PropsBackedAuthenticator.java b/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/PropsBackedAuthenticator.java
new file mode 100644
index 0000000..6076fbf
--- /dev/null
+++ b/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/PropsBackedAuthenticator.java
@@ -0,0 +1,48 @@
+package com.vmware.gemfire.tools.pulse.tests;
+
+import java.util.Collections;
+
+import javax.management.remote.JMXAuthenticator;
+import javax.management.remote.JMXPrincipal;
+import javax.security.auth.Subject;
+
+/**
+ * 
+ * @author tushark
+ *
+ */
+public class PropsBackedAuthenticator extends JMXBaseBean implements JMXAuthenticator {
+
+  @Override
+  public Subject authenticate(Object credentials) {
+    String username = null, password = null;
+    if (credentials instanceof String[]) {
+      final String[] aCredentials = (String[]) credentials;
+      username = (String) aCredentials[0];
+      password = (String) aCredentials[1];
+      System.out.println("#intSec User="+ username + " password="+password);
+      String users[] = getStringArray("users");
+      for(String u : users) {
+        if(username.equals(u)) {          
+          String storedpassword = getString("users."+u+".password");
+          System.out.println("#intSec PropUser="+ u + " PropPassword="+storedpassword);
+          if(storedpassword!=null && storedpassword.equals(password)) {
+            return new Subject(true, Collections.singleton(new JMXPrincipal(username)), Collections.EMPTY_SET,
+                Collections.EMPTY_SET);
+          } else {
+            throw new SecurityException("Authentication Failed 1");
+          }
+        }
+      }
+    } else {
+      throw new SecurityException("Credentials Missing");
+    }
+    throw new SecurityException("Authentication Failed 2");
+  }
+
+  @Override
+  protected String getKey(String propName) {    
+    return propName;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/87c83da2/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/PulseTests.java
----------------------------------------------------------------------
diff --git a/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/PulseTests.java b/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/PulseTests.java
index b6dd7c6..c660ba7 100644
--- a/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/PulseTests.java
+++ b/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/PulseTests.java
@@ -131,7 +131,7 @@ public class PulseTests {
   @BeforeClass
   public static void setUpBeforeClass() throws Exception {
     try {
-      server = Server.createServer(9999, jmxPropertiesFile);
+      server = Server.createServer(9999, jmxPropertiesFile, false);
 
       String host = "localhost";// InetAddress.getLocalHost().getHostAddress();
       int port = 8080;

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/87c83da2/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/Server.java
----------------------------------------------------------------------
diff --git a/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/Server.java b/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/Server.java
index 42373c9..d20e10f 100644
--- a/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/Server.java
+++ b/pulse/src/test/java/com/vmware/gemfire/tools/pulse/tests/Server.java
@@ -7,23 +7,26 @@
  */
 package com.vmware.gemfire.tools.pulse.tests;
 
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.Set;
-
-import javax.management.InstanceAlreadyExistsException;
-import javax.management.MBeanRegistrationException;
-import javax.management.MBeanServer;
-import javax.management.MBeanServerFactory;
-import javax.management.MalformedObjectNameException;
-import javax.management.NotCompliantMBeanException;
-import javax.management.ObjectName;
-import javax.management.remote.JMXConnectorServer;
-import javax.management.remote.JMXConnectorServerFactory;
-import javax.management.remote.JMXServiceURL;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.ObjectName;
+import javax.management.remote.JMXAuthenticator;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
 
 public class Server {
   private static final String DEFAULT_HOST = "127.0.0.1"; //"localhost"
@@ -33,7 +36,7 @@ public class Server {
   private final JMXConnectorServer cs;
   private String propFile = null;
 
-  public Server(int port, String properties) throws IOException {
+  public Server(int port, String properties, boolean startIntSec) throws IOException {
     try {
       java.rmi.registry.LocateRegistry.createRegistry(port);
       System.out.println("RMI registry ready.");
@@ -44,14 +47,39 @@ public class Server {
 
     this.propFile = properties;
     mbs = MBeanServerFactory.createMBeanServer();
-    url = new JMXServiceURL(formJMXServiceURLString(DEFAULT_HOST, "" + port));
-    cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
-
-    cs.start();
-
-    loadMBeans();
-  }
-
+    url = new JMXServiceURL(formJMXServiceURLString(DEFAULT_HOST, "" + port));
+    
+    if(!startIntSec) {
+      cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
+    } else {
+      Map<String,Object> env = new HashMap<String,Object>();
+      env.put(JMXConnectorServer.AUTHENTICATOR, new PropsBackedAuthenticator());
+      cs = JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
+      System.out.println("#intSec Using PropsBackedAuthenticator");
+    }
+    
+    cs.start();   
+    loadMBeans();
+    if(startIntSec)
+      loadAccessControllerMBean();
+  }
+    
+
+  private void loadAccessControllerMBean() {
+    AccessControl ac = new AccessControl();
+    try {
+      mbs.registerMBean(ac, new ObjectName(AccessControl.OBJECT_NAME_ACCESSCONTROL));
+    } catch (InstanceAlreadyExistsException e) {
+      e.printStackTrace();
+    } catch (MBeanRegistrationException e) {
+      e.printStackTrace();
+    } catch (NotCompliantMBeanException e) {
+      e.printStackTrace();
+    } catch (MalformedObjectNameException e) {
+      e.printStackTrace();
+    }    
+  }
+
   private String formJMXServiceURLString(String host, String port)
       throws UnknownHostException {
     /*
@@ -330,14 +358,14 @@ public class Server {
       }
     }
 
-    createServer(port, props);
+    createServer(port, props, false);
   }
 
-  public static Server createServer(int port, String properties)
+  public static Server createServer(int port, String properties, boolean intSecEnabled)
       throws MalformedObjectNameException {
     Server s = null;
     try {
-      s = new Server(port, properties);
+      s = new Server(port, properties, intSecEnabled);
     } catch (Exception e) {
       e.printStackTrace();
       return null;

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/87c83da2/pulse/src/test/resources/test.properties
----------------------------------------------------------------------
diff --git a/pulse/src/test/resources/test.properties b/pulse/src/test/resources/test.properties
index fabeea1..67f51bb 100644
--- a/pulse/src/test/resources/test.properties
+++ b/pulse/src/test/resources/test.properties
@@ -6,6 +6,18 @@ members=M1 M2 M3
 gemfirexdmembers=M1 M2 M3
 aggregatequeries=Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 Q10 Q11 Q12 Q13 Q14 Q15 Q16 Q17 Q18 Q19 Q20 Q21 Q22 Q23 Q24 Q25
 
+# Integrated security
+users=dataRead dataWrite admin
+
+users.dataRead.password=dataRead
+users.dataRead.roles=PULSE_DASHBOARD
+
+users.dataWrite.password=dataWrite
+users.dataWrite.roles=PULSE_DASHBOARD PULSE_DATABROWSER
+
+users.admin.password=admin
+users.admin.roles=PULSE_DASHBOARD PULSE_DATABROWSER
+
 server.S1.listCacheServers=true
 server.S1.memberCount=3
 server.S1.numClients=2