You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by gr...@apache.org on 2014/10/07 15:08:28 UTC

[2/4] git commit: BROOKLYN-71: DelegateSecurityProvider listens to property changes

BROOKLYN-71: DelegateSecurityProvider listens to property changes

Reloads the provider when notified and forces existing sessions to
reauthenticate.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/27ff1172
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/27ff1172
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/27ff1172

Branch: refs/heads/master
Commit: 27ff1172ea3586a9b97b1e793443af6f14174c26
Parents: c0fec39
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Fri Oct 3 16:30:00 2014 +0100
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Fri Oct 3 16:30:00 2014 +0100

----------------------------------------------------------------------
 .../main/java/brooklyn/rest/api/ServerApi.java  |   2 -
 .../BrooklynPropertiesSecurityFilter.java       | 134 +++++++++----------
 .../provider/AbstractSecurityProvider.java      |   6 +-
 .../provider/DelegatingSecurityProvider.java    |  82 ++++++++----
 .../provider/ExplicitUsersSecurityProvider.java |  23 ++--
 .../ServerResourceIntegrationTest.java          |  41 +++++-
 .../security/provider/TestSecurityProvider.java |   4 +
 7 files changed, 183 insertions(+), 109 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/27ff1172/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
index 7f4e486..ab56046 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
@@ -38,8 +38,6 @@ import com.wordnik.swagger.core.ApiParam;
 @Path("/v1/server")
 @Apidoc("Server")
 @Produces(MediaType.APPLICATION_JSON)
-@Consumes(MediaType.APPLICATION_JSON)
-
 @Beta
 public interface ServerApi {
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/27ff1172/usage/rest-server/src/main/java/brooklyn/rest/security/BrooklynPropertiesSecurityFilter.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/security/BrooklynPropertiesSecurityFilter.java b/usage/rest-server/src/main/java/brooklyn/rest/security/BrooklynPropertiesSecurityFilter.java
index 249b515..815f080 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/security/BrooklynPropertiesSecurityFilter.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/security/BrooklynPropertiesSecurityFilter.java
@@ -71,77 +71,75 @@ public class BrooklynPropertiesSecurityFilter implements Filter {
 
     @Override
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        HttpServletRequest httpRequest = (HttpServletRequest) request;
+        HttpServletResponse httpResponse = (HttpServletResponse) response;
+
         if (provider == null) {
             log.warn("No security provider available: disallowing web access to brooklyn");
-            ((HttpServletResponse) response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+            httpResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
             return;
         }
 
-        HttpServletRequest httpRequest = (HttpServletRequest) request;
-        if (authenticate(httpRequest)) {
-            String user = Strings.toString(httpRequest.getSession().getAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE));
-            if (handleLogout(httpRequest)) {
-                log.debug("REST logging out " + user + " of session " + httpRequest.getSession());
-                // do nothing here, fall through to below
+        if (handleLogout(httpRequest) || !authenticate(httpRequest)) {
+            httpResponse.setHeader("WWW-Authenticate", "Basic realm=\"brooklyn\"");
+            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return;
+        }
+
+        WebEntitlementContext entitlementContext = null;
+        String uri = httpRequest.getRequestURI();
+        String uid = Identifiers.makeRandomId(6);
+        String user = Strings.toString(httpRequest.getSession().getAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE));
+        try {
+            entitlementContext = new WebEntitlementContext(user, httpRequest.getRemoteAddr(), uri, uid);
+            if (originalRequest.get() == null) {
+                // initial filter application
+                originalRequest.set(uri);
             } else {
-                WebEntitlementContext entitlementContext = null;
-                String uri = httpRequest.getRequestURI();
-                try {
-                    String uid = Identifiers.makeRandomId(6);
-                    entitlementContext = new WebEntitlementContext(user, httpRequest.getRemoteAddr(), uri, uid);
-                    if (originalRequest.get() == null) {
-                        // initial filter application
-                        originalRequest.set(uri);
-                    } else {
-                        // this filter is being applied *again*, probably due to forwarding (e.g. from '/' to '/index.html')
-                        log.debug("REST request {} being forwarded from {} to {}", new Object[]{uid, originalRequest.get(), uri});
-                        // clear the entitlement context before setting to avoid warnings
-                        Entitlements.clearEntitlementContext();
-                    }
-                    Entitlements.setEntitlementContext(entitlementContext);
-
-                    log.debug("REST req {} starting processing request {} with {}", new Object[]{uid, uri, entitlementContext});
-                    chain.doFilter(request, response);
-
-                    // This logging must NOT happen before chain.doFilter, or FormMapProvider will not work as expected.
-                    // Getting the parameter map consumes the request body and only resource methods using @FormParam
-                    // will work as expected.
-                    if (log.isDebugEnabled()) {
-                        log.debug("REST req {} complete, responding {} for request {} with {}",
-                                new Object[]{uid, ((HttpServletResponse) response).getStatus(), uri, entitlementContext});
-                        log.debug("  source: " + httpRequest.getRemoteAddr());
-                        if (!httpRequest.getParameterMap().isEmpty()) {
-                            log.debug("  parameters were: {}", httpRequest.getParameterMap());
-                        }
-                        if (httpRequest.getContentLength() > 0) {
-                            int len = httpRequest.getContentLength();
-                            log.debug("  upload content type was {}, length={}", httpRequest.getContentType(), len);
-                        }
-                        if (log.isTraceEnabled()) {
-                            Enumeration<String> headerNames = httpRequest.getHeaderNames();
-                            if (headerNames.hasMoreElements()) {
-                                log.trace("  headers:");
-                                while (headerNames.hasMoreElements()) {
-                                    String headerName = headerNames.nextElement();
-                                    log.trace("    {}: {}", headerName, httpRequest.getHeader(headerName));
-                                }
-                            }
+                // this filter is being applied *again*, probably due to forwarding (e.g. from '/' to '/index.html')
+                log.debug("REST {} being forwarded from {} to {}", new Object[]{uid, originalRequest.get(), uri});
+                // clear the entitlement context before setting to avoid warnings
+                Entitlements.clearEntitlementContext();
+            }
+            Entitlements.setEntitlementContext(entitlementContext);
+
+            log.debug("REST {} starting processing request {} with {}", new Object[]{uid, uri, entitlementContext});
+            chain.doFilter(request, response);
+
+            // This logging must NOT happen before chain.doFilter, or FormMapProvider will not work as expected.
+            // Getting the parameter map consumes the request body and only resource methods using @FormParam
+            // will work as expected.
+            if (log.isDebugEnabled()) {
+                log.debug("REST {} complete, responding {} for `request {} with {}",
+                        new Object[]{uid, httpResponse.getStatus(), uri, entitlementContext});
+                log.debug("  source: " + httpRequest.getRemoteAddr());
+                if (!httpRequest.getParameterMap().isEmpty()) {
+                    log.debug("  parameters were: {}", httpRequest.getParameterMap());
+                }
+                if (httpRequest.getContentLength() > 0) {
+                    int len = httpRequest.getContentLength();
+                    log.debug("  upload content type was {}, length={}", httpRequest.getContentType(), len);
+                }
+                if (log.isTraceEnabled()) {
+                    Enumeration<String> headerNames = httpRequest.getHeaderNames();
+                    if (headerNames.hasMoreElements()) {
+                        log.trace("  headers:");
+                        while (headerNames.hasMoreElements()) {
+                            String headerName = headerNames.nextElement();
+                            log.trace("    {}: {}", headerName, httpRequest.getHeader(headerName));
                         }
                     }
-                    return;
-                } catch (Throwable e) {
-                    // errors are typically already caught at this point, except for serialization errors
-                    log.warn("REST failed processing request " + uri + " with " + entitlementContext + ": " + e, e);
-                    ((HttpServletResponse) response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-                    return;
-                } finally {
-                    originalRequest.remove();
-                    Entitlements.clearEntitlementContext();
                 }
             }
+        } catch (Throwable e) {
+            // errors are typically already caught at this point, except for serialization errors
+            log.warn("REST " + uid + " failed processing request " + uri + " with " + entitlementContext + ": " + e, e);
+            httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        } finally {
+            originalRequest.remove();
+            Entitlements.clearEntitlementContext();
         }
-        ((HttpServletResponse) response).setHeader("WWW-Authenticate", "Basic realm=\"brooklyn\"");
-        ((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
+
     }
 
     protected boolean authenticate(HttpServletRequest request) {
@@ -152,14 +150,14 @@ public class BrooklynPropertiesSecurityFilter implements Filter {
         session.setAttribute(REMOTE_ADDRESS_SESSION_ATTRIBUTE, request.getRemoteAddr());
         String user = null, pass = null;
         String authorization=request.getHeader("Authorization");
-        if (authorization!=null) {
-            String userpass=Base64.base64Decode(authorization.substring(6));
-            user=userpass.substring(0,userpass.indexOf(":"));
-            pass=userpass.substring(userpass.indexOf(":")+1);
+        if (authorization != null) {
+            String userpass = Base64.base64Decode(authorization.substring(6));
+            user = userpass.substring(0, userpass.indexOf(":"));
+            pass = userpass.substring(userpass.indexOf(":") + 1);
         }
         if (provider.authenticate(session, user, pass)) {
-            log.debug("Web API authenticated "+session+" for user "+user);
-            if (user!=null) {
+            log.debug("REST authenticated {} for user {}", session.getId(), user);
+            if (user != null) {
                 session.setAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE, user);
             }
             return true;
@@ -180,8 +178,8 @@ public class BrooklynPropertiesSecurityFilter implements Filter {
 
     protected boolean handleLogout(HttpServletRequest request) {
         if ("/logout".equals(request.getRequestURI()) || "/v1/logout".equals(request.getRequestURI())) {
-            log.info("Web API logging out "+request.getSession()+" for user "+
-                    request.getSession().getAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE));
+            log.info("Web API logging {} out of session {}",
+                    request.getSession().getAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE), request.getSession().getId());
             provider.logout(request.getSession());
             request.getSession().removeAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE);
             request.getSession().removeAttribute(REMOTE_ADDRESS_SESSION_ATTRIBUTE);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/27ff1172/usage/rest-server/src/main/java/brooklyn/rest/security/provider/AbstractSecurityProvider.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/security/provider/AbstractSecurityProvider.java b/usage/rest-server/src/main/java/brooklyn/rest/security/provider/AbstractSecurityProvider.java
index 8b72e2e..fa302ce 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/security/provider/AbstractSecurityProvider.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/security/provider/AbstractSecurityProvider.java
@@ -29,7 +29,7 @@ import org.slf4j.LoggerFactory;
  */
 public abstract class AbstractSecurityProvider implements SecurityProvider {
 
-    public static final Logger LOG = LoggerFactory.getLogger(AbstractSecurityProvider.class);
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractSecurityProvider.class);
 
     @Override
     public boolean isAuthenticated(HttpSession session) {
@@ -45,11 +45,12 @@ public abstract class AbstractSecurityProvider implements SecurityProvider {
         return true;
     }
 
+
     /**
      * Sets an authentication token for the user on the session. Always returns true.
      */
     protected boolean allow(HttpSession session, String user) {
-        LOG.debug("Web console {} authenticated user {}", getClass().getSimpleName(), user);
+        LOG.debug("REST {} authenticated user {}", getClass().getSimpleName(), user);
         session.setAttribute(getAuthenticationKey(), user);
         return true;
     }
@@ -58,4 +59,5 @@ public abstract class AbstractSecurityProvider implements SecurityProvider {
         return getClass().getName() + ".AUTHENTICATED";
     }
 
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/27ff1172/usage/rest-server/src/main/java/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/security/provider/DelegatingSecurityProvider.java b/usage/rest-server/src/main/java/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
index 2b27bda..306008a 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
@@ -19,7 +19,7 @@
 package brooklyn.rest.security.provider;
 
 import java.lang.reflect.Constructor;
-
+import java.util.concurrent.atomic.AtomicLong;
 import javax.servlet.http.HttpSession;
 
 import org.slf4j.Logger;
@@ -37,62 +37,96 @@ public class DelegatingSecurityProvider implements SecurityProvider {
 
     public DelegatingSecurityProvider(ManagementContext mgmt) {
         this.mgmt = mgmt;
+        mgmt.addPropertiesReloadListener(new PropertiesListener());
     }
     
-    private SecurityProvider targetProvider;
+    private SecurityProvider delegate;
+    private final AtomicLong modCount = new AtomicLong();
 
-    @SuppressWarnings("unchecked")
-    public synchronized SecurityProvider getTargetProvider() {
-        if (this.targetProvider!=null) {
-            return targetProvider;
+    private class PropertiesListener implements ManagementContext.PropertiesReloadListener {
+        @Override
+        public void reloaded() {
+            log.debug("{} reloading security provider", DelegatingSecurityProvider.this);
+            synchronized (DelegatingSecurityProvider.this) {
+                loadDelegate();
+                invalidateExistingSessions();
+            }
         }
+    }
 
+    public synchronized SecurityProvider getDelegate() {
+        if (delegate == null) {
+            delegate = loadDelegate();
+        }
+        return delegate;
+    }
+
+    @SuppressWarnings("unchecked")
+    private synchronized SecurityProvider loadDelegate() {
         StringConfigMap brooklynProperties = mgmt.getConfig();
 
         String className = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME);
-        log.info("Web console using security provider "+className);
+        log.info("REST using security provider " + className);
 
         try {
             Class<? extends SecurityProvider> clazz;
             try {
-                clazz = (Class<? extends SecurityProvider>)Class.forName(className);
+                clazz = (Class<? extends SecurityProvider>) Class.forName(className);
             } catch (Exception e) {
-                String OLD_PACKAGE = "brooklyn.web.console.security.";
-                if (className.startsWith(OLD_PACKAGE)) {
-                    className = Strings.removeFromStart(className, OLD_PACKAGE);
-                    className = DelegatingSecurityProvider.class.getPackage().getName()+"."+className;
-                    clazz = (Class<? extends SecurityProvider>)Class.forName(className);
-                    log.warn("Deprecated package "+OLD_PACKAGE+" detected; please update security provider to point to "+className);
+                String oldPackage = "brooklyn.web.console.security.";
+                if (className.startsWith(oldPackage)) {
+                    className = Strings.removeFromStart(className, oldPackage);
+                    className = DelegatingSecurityProvider.class.getPackage().getName() + "." + className;
+                    clazz = (Class<? extends SecurityProvider>) Class.forName(className);
+                    log.warn("Deprecated package " + oldPackage + " detected; please update security provider to point to " + className);
                 } else throw e;
             }
-            
+
             Constructor<? extends SecurityProvider> constructor;
             try {
                 constructor = clazz.getConstructor(ManagementContext.class);
-                targetProvider = constructor.newInstance(mgmt);
+                delegate = constructor.newInstance(mgmt);
             } catch (Exception e) {
                 constructor = clazz.getConstructor();
-                targetProvider = constructor.newInstance();
+                delegate = constructor.newInstance();
             }
         } catch (Exception e) {
-            log.warn("Web console unable to instantiate security provider "+className+"; all logins are being disallowed",e);
-            targetProvider = new BlackholeSecurityProvider();
+            log.warn("REST unable to instantiate security provider " + className + "; all logins are being disallowed", e);
+            delegate = new BlackholeSecurityProvider();
         }
-        return targetProvider;
+        return delegate;
     }
-    
+
+    /**
+     * Causes all existing sessions to be invalidated.
+     */
+    protected void invalidateExistingSessions() {
+        modCount.incrementAndGet();
+    }
+
     @Override
     public boolean isAuthenticated(HttpSession session) {
-        return getTargetProvider().isAuthenticated(session);
+        Object modCountWhenFirstAuthenticated = session.getAttribute(getModificationCountKey());
+        boolean authenticated = getDelegate().isAuthenticated(session) &&
+                modCountWhenFirstAuthenticated != null && ((Long) modCount.get()).equals(modCountWhenFirstAuthenticated);
+        if (authenticated) {
+            session.setAttribute(getModificationCountKey(), modCount.get());
+        }
+        return authenticated;
     }
 
     @Override
     public boolean authenticate(HttpSession session, String user, String password) {
-        return getTargetProvider().authenticate(session, user, password);
+        return getDelegate().authenticate(session, user, password);
     }
 
     @Override
     public boolean logout(HttpSession session) { 
-        return getTargetProvider().logout(session);
+        return getDelegate().logout(session);
+    }
+
+    private String getModificationCountKey() {
+        return getClass().getName() + ".ModCount";
     }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/27ff1172/usage/rest-server/src/main/java/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java b/usage/rest-server/src/main/java/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
index 3741b29..a3cc2f7 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
@@ -48,25 +48,26 @@ public class ExplicitUsersSecurityProvider extends AbstractSecurityProvider impl
     }
 
     private synchronized void initialize() {
-        if (allowedUsers!=null) return;
+        if (allowedUsers != null) return;
 
         StringConfigMap properties = mgmt.getConfig();
 
         allowedUsers = new LinkedHashSet<String>();
         String users = properties.getConfig(BrooklynWebConfig.USERS);
-        if (users==null) {
-            LOG.warn("Web console has no users configured; no one will be able to log in!");
+        if (users == null) {
+            LOG.warn("REST has no users configured; no one will be able to log in!");
         } else if ("*".equals(users)) {
-            LOG.info("Web console allowing any user (so long as valid password is set)");
+            LOG.info("REST allowing any user (so long as valid password is set)");
             allowAnyUserWithValidPass = true;
         } else {
             StringTokenizer t = new StringTokenizer(users, ",");
             while (t.hasMoreElements()) {
-                allowedUsers.add((""+t.nextElement()).trim());
+                allowedUsers.add(("" + t.nextElement()).trim());
             }
-            LOG.info("Web console allowing users: "+allowedUsers);
-        }       
+            LOG.info("REST allowing users: " + allowedUsers);
+        }
     }
+
     
     @Override
     public boolean authenticate(HttpSession session, String user, String password) {
@@ -76,7 +77,7 @@ public class ExplicitUsersSecurityProvider extends AbstractSecurityProvider impl
         
         if (!allowAnyUserWithValidPass) {
             if (!allowedUsers.contains(user)) {
-                LOG.info("Web console rejecting unknown user "+user);
+                LOG.debug("REST rejecting unknown user "+user);
                 return false;                
             }
         }
@@ -91,7 +92,7 @@ public class ExplicitUsersSecurityProvider extends AbstractSecurityProvider impl
                 // password is good
                 return allow(session, user);
             } else {
-                LOG.info("Web console rejecting bad password for user "+user);
+                LOG.debug("REST rejecting bad password for user "+user);
                 return false;
             }
         }
@@ -101,11 +102,11 @@ public class ExplicitUsersSecurityProvider extends AbstractSecurityProvider impl
                 // hashed password is good
                 return allow(session, user);
             } else {
-                LOG.info("Web console rejecting bad password for user "+user);
+                LOG.debug("REST rejecting bad password for user "+user);
                 return false;
             }                
         }
-        LOG.warn("Web console rejecting passwordless user " + user);
+        LOG.debug("REST rejecting passwordless user " + user);
         return false;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/27ff1172/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerResourceIntegrationTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerResourceIntegrationTest.java
index f435bef..3244cb4 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerResourceIntegrationTest.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerResourceIntegrationTest.java
@@ -21,7 +21,10 @@ package brooklyn.rest.resources;
 import static org.testng.Assert.assertEquals;
 
 import java.net.URI;
+import java.util.Collections;
+import java.util.Map;
 
+import org.apache.http.HttpStatus;
 import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.HttpClient;
 import org.eclipse.jetty.server.Server;
@@ -29,16 +32,50 @@ import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableMap;
 
+import brooklyn.entity.BrooklynAppLiveTestSupport;
+import brooklyn.management.ManagementContext;
 import brooklyn.rest.BrooklynRestApiLauncher;
 import brooklyn.rest.BrooklynRestApiLauncherTestFixture;
+import brooklyn.rest.security.provider.AnyoneSecurityProvider;
 import brooklyn.rest.security.provider.TestSecurityProvider;
 import brooklyn.test.HttpTestUtils;
+import brooklyn.test.entity.LocalManagementContextForTests;
 import brooklyn.util.http.HttpTool;
 import brooklyn.util.http.HttpToolResponse;
 
 public class ServerResourceIntegrationTest extends BrooklynRestApiLauncherTestFixture {
 
-    // marked integration because of time
+    /**
+     * [sam] Other tests rely on brooklyn.properties not containing security properties so ..
+     * I think the best way to test this is to set a security provider, then reload properties
+     * and check no authentication is required.
+     */
+    @Test(groups = "Integration")
+    public void testSecurityProviderUpdatesWhenPropertiesReloaded() {
+        Server server = useServerForTest(BrooklynRestApiLauncher.launcher()
+                .withoutJsgui()
+                .securityProvider(TestSecurityProvider.class)
+                .start());
+        HttpTool.HttpClientBuilder builder = HttpTool.httpClientBuilder()
+                .uri(getBaseUri(server));
+
+        HttpToolResponse response;
+        final URI uri = URI.create(getBaseUri() + "/v1/server/properties/reload");
+        final Map<String, String> args = Collections.emptyMap();
+
+        // Unauthorised
+        response = HttpTool.httpPost(builder.build(), uri, args, args);
+        assertEquals(response.getResponseCode(), HttpStatus.SC_UNAUTHORIZED);
+
+        response = HttpTool.httpPost(builder.credentials(TestSecurityProvider.CREDENTIAL).build(),
+                uri, args, args);
+        HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+
+        // Lack of credentials now accepted.
+        response = HttpTool.httpPost(builder.build(), uri, args, args);
+        HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+    }
+
     @Test(groups = "Integration")
     public void testGetUser() throws Exception {
         Server server = useServerForTest(BrooklynRestApiLauncher.launcher()
@@ -51,7 +88,7 @@ public class ServerResourceIntegrationTest extends BrooklynRestApiLauncherTestFi
     private String getServerUser(Server server) throws Exception {
         HttpClient client = HttpTool.httpClientBuilder()
                 .uri(getBaseUri(server))
-                .credentials(new UsernamePasswordCredentials(TestSecurityProvider.USER, TestSecurityProvider.PASSWORD))
+                .credentials(TestSecurityProvider.CREDENTIAL)
                 .build();
         HttpToolResponse response = HttpTool.httpGet(client, URI.create(getBaseUri() + "/v1/server/user"),
                 ImmutableMap.<String, String>of());

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/27ff1172/usage/rest-server/src/test/java/brooklyn/rest/security/provider/TestSecurityProvider.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/security/provider/TestSecurityProvider.java b/usage/rest-server/src/test/java/brooklyn/rest/security/provider/TestSecurityProvider.java
index ccb89a6..6377c76 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/security/provider/TestSecurityProvider.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/security/provider/TestSecurityProvider.java
@@ -20,10 +20,14 @@ package brooklyn.rest.security.provider;
 
 import javax.servlet.http.HttpSession;
 
+import org.apache.http.auth.UsernamePasswordCredentials;
+
 public class TestSecurityProvider implements SecurityProvider {
 
     public static final String USER = "test";
     public static final String PASSWORD = "opensesame";
+    public static final UsernamePasswordCredentials CREDENTIAL =
+            new UsernamePasswordCredentials(TestSecurityProvider.USER, TestSecurityProvider.PASSWORD);
 
     @Override
     public boolean isAuthenticated(HttpSession session) {