You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by sv...@apache.org on 2016/02/10 15:09:01 UTC

[3/4] brooklyn-server git commit: Add entitlements checks for sensor/config lookup

Add entitlements checks for sensor/config lookup


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

Branch: refs/heads/master
Commit: a0557178d92efbad7b8f176b84b3f4d56da804d3
Parents: bb8980a
Author: Aled Sage <al...@gmail.com>
Authored: Tue Feb 9 17:27:40 2016 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Wed Feb 10 12:42:52 2016 +0000

----------------------------------------------------------------------
 .../core/mgmt/entitlement/Entitlements.java     |  18 ++-
 .../mgmt/entitlement/EntityEntitlementTest.java |   4 +
 .../rest/resources/EntityConfigResource.java    |  73 ++++++++----
 .../brooklyn/rest/resources/SensorResource.java |  55 +++++++--
 .../AbstractRestApiEntitlementsTest.java        | 111 +++++++++++++++++++
 .../AuthenticateAnyoneSecurityProvider.java     |  41 +++++++
 .../EntityConfigApiEntitlementsTest.java        | 103 +++++++++++++++++
 .../entitlement/SensorApiEntitlementsTest.java  | 108 ++++++++++++++++++
 .../entitlement/ServerApiEntitlementsTest.java  |  34 ++++++
 .../StaticDelegatingEntitlementManager.java     |  37 +++++++
 10 files changed, 554 insertions(+), 30 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a0557178/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/Entitlements.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/Entitlements.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/Entitlements.java
index 6c281fc..d3f9687 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/Entitlements.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/Entitlements.java
@@ -66,6 +66,7 @@ public class Entitlements {
     
     public static EntitlementClass<Entity> SEE_ENTITY = new BasicEntitlementClassDefinition<Entity>("entity.see", Entity.class);
     public static EntitlementClass<EntityAndItem<String>> SEE_SENSOR = new BasicEntitlementClassDefinition<EntityAndItem<String>>("sensor.see", EntityAndItem. typeToken(String.class));
+    public static EntitlementClass<EntityAndItem<String>> SEE_CONFIG = new BasicEntitlementClassDefinition<EntityAndItem<String>>("config.see", EntityAndItem. typeToken(String.class));
     // string is effector name; argument may be a map or a list, depending how the args were supplied
     public static EntitlementClass<EntityAndItem<StringAndArgument>> INVOKE_EFFECTOR = new BasicEntitlementClassDefinition<EntityAndItem<StringAndArgument>>("effector.invoke", EntityAndItem.typeToken(StringAndArgument.class));
     public static EntitlementClass<Entity> MODIFY_ENTITY = new BasicEntitlementClassDefinition<Entity>("entity.modify", Entity.class);
@@ -274,6 +275,7 @@ public class Entitlements {
                 return "Entitlements.allowing(" + permission + " -> " + test + ")";
             }
         }
+        
         public static EntitlementManager seeNonSecretSensors() {
             return allowing(SEE_SENSOR, new Predicate<EntityAndItem<String>>() {
                 @Override
@@ -288,13 +290,27 @@ public class Entitlements {
             });
         }
         
+        public static EntitlementManager seeNonSecretConfig() {
+            return allowing(SEE_CONFIG, new Predicate<EntityAndItem<String>>() {
+                @Override
+                public boolean apply(EntityAndItem<String> input) {
+                    if (input == null) return false;
+                    return !Sanitizer.IS_SECRET_PREDICATE.apply(input.getItem());
+                }
+                @Override
+                public String toString() {
+                    return "Predicates.nonSecret";
+                }
+            });
+        }
     }
     
     /** allow read-only */
     public static EntitlementManager readOnly() {
         return FineGrainedEntitlements.anyOf(
             FineGrainedEntitlements.allowing(SEE_ENTITY),
-            FineGrainedEntitlements.seeNonSecretSensors()
+            FineGrainedEntitlements.seeNonSecretSensors(),
+            FineGrainedEntitlements.seeNonSecretConfig()
         );
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a0557178/core/src/test/java/org/apache/brooklyn/core/mgmt/entitlement/EntityEntitlementTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/entitlement/EntityEntitlementTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/entitlement/EntityEntitlementTest.java
index 933d117..6a0a8d0 100644
--- a/core/src/test/java/org/apache/brooklyn/core/mgmt/entitlement/EntityEntitlementTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/entitlement/EntityEntitlementTest.java
@@ -70,6 +70,7 @@ public class EntityEntitlementTest {
         Assert.assertTrue(mgmt.getEntitlementManager().isEntitled(null, Entitlements.SEE_ENTITY, app));
         Assert.assertTrue(mgmt.getEntitlementManager().isEntitled(null, Entitlements.INVOKE_EFFECTOR, EntityAndItem.of(app, StringAndArgument.of("any-eff", null))));
         Assert.assertTrue(mgmt.getEntitlementManager().isEntitled(null, Entitlements.SEE_SENSOR, EntityAndItem.of(app, "any-sensor")));
+        Assert.assertTrue(mgmt.getEntitlementManager().isEntitled(null, Entitlements.SEE_CONFIG, EntityAndItem.of(app, "any-config")));
         // and can invoke methods, without any user/login registered
         confirmEffectorEntitlement(true);
         confirmSensorEntitlement(true);
@@ -83,6 +84,7 @@ public class EntityEntitlementTest {
         Assert.assertTrue(mgmt.getEntitlementManager().isEntitled(null, Entitlements.SEE_ENTITY, app));
         Assert.assertTrue(mgmt.getEntitlementManager().isEntitled(null, Entitlements.INVOKE_EFFECTOR, EntityAndItem.of(app, StringAndArgument.of("any-eff", null))));
         Assert.assertTrue(mgmt.getEntitlementManager().isEntitled(null, Entitlements.SEE_SENSOR, EntityAndItem.of(app, "any-sensor")));
+        Assert.assertTrue(mgmt.getEntitlementManager().isEntitled(null, Entitlements.SEE_CONFIG, EntityAndItem.of(app, "any-config")));
         
         confirmEffectorEntitlement(true);
         confirmSensorEntitlement(true);
@@ -121,6 +123,7 @@ public class EntityEntitlementTest {
         Assert.assertTrue(mgmt.getEntitlementManager().isEntitled(null, Entitlements.SEE_ENTITY, app));
         Assert.assertFalse(mgmt.getEntitlementManager().isEntitled(null, Entitlements.INVOKE_EFFECTOR, EntityAndItem.of(app, StringAndArgument.of("any-eff", null))));
         Assert.assertTrue(mgmt.getEntitlementManager().isEntitled(null, Entitlements.SEE_SENSOR, EntityAndItem.of(app, "any-sensor")));
+        Assert.assertTrue(mgmt.getEntitlementManager().isEntitled(null, Entitlements.SEE_CONFIG, EntityAndItem.of(app, "any-config")));
         
         confirmEffectorEntitlement(false);
         confirmSensorEntitlement(true);
@@ -134,6 +137,7 @@ public class EntityEntitlementTest {
         Assert.assertFalse(mgmt.getEntitlementManager().isEntitled(null, Entitlements.SEE_ENTITY, app));
         Assert.assertFalse(mgmt.getEntitlementManager().isEntitled(null, Entitlements.INVOKE_EFFECTOR, EntityAndItem.of(app, StringAndArgument.of("any-eff", null))));
         Assert.assertFalse(mgmt.getEntitlementManager().isEntitled(null, Entitlements.SEE_SENSOR, EntityAndItem.of(app, "any-sensor")));
+        Assert.assertFalse(mgmt.getEntitlementManager().isEntitled(null, Entitlements.SEE_CONFIG, EntityAndItem.of(app, "any-config")));
         
         confirmEffectorEntitlement(false);
         confirmSensorEntitlement(false);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a0557178/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java
index a6d3b8e..e6dd315 100644
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java
+++ b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java
@@ -18,18 +18,18 @@
  */
 package org.apache.brooklyn.rest.resources;
 
-import static com.google.common.collect.Iterables.transform;
-
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 
 import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.BasicConfigKey;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
 import org.apache.brooklyn.rest.api.EntityConfigApi;
 import org.apache.brooklyn.rest.domain.EntityConfigSummary;
 import org.apache.brooklyn.rest.filter.HaHotStateRequired;
@@ -43,7 +43,6 @@ import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Function;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
@@ -56,15 +55,26 @@ public class EntityConfigResource extends AbstractBrooklynRestResource implement
     @Override
     public List<EntityConfigSummary> list(final String application, final String entityToken) {
         final Entity entity = brooklyn().getEntity(application, entityToken);
-        // TODO merge with keys which have values
-        return Lists.newArrayList(transform(
-                entity.getEntityType().getConfigKeys(),
-                new Function<ConfigKey<?>, EntityConfigSummary>() {
-                    @Override
-                    public EntityConfigSummary apply(ConfigKey<?> config) {
-                        return EntityTransformer.entityConfigSummary(entity, config);
-                    }
-                }));
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            throw WebResourceUtils.unauthorized("User '%s' is not authorized to see entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+
+        // TODO merge with keys which have values:
+        //      ((EntityInternal) entity).config().getBag().getAllConfigAsConfigKeyMap();
+        List<EntityConfigSummary> result = Lists.newArrayList();
+        
+        for (ConfigKey<?> key : entity.getEntityType().getConfigKeys()) {
+            // Exclude config that user is not allowed to see
+            if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CONFIG, new EntityAndItem<String>(entity, key.getName()))) {
+                LOG.trace("User {} not authorized to see config {} of entity {}; excluding from ConfigKey list results", 
+                        new Object[] {Entitlements.getEntitlementContext().user(), key.getName(), entity});
+                continue;
+            }
+            result.add(EntityTransformer.entityConfigSummary(entity, key));
+        }
+        
+        return result;
     }
 
     // TODO support parameters  ?show=value,summary&name=xxx &format={string,json,xml}
@@ -73,16 +83,23 @@ public class EntityConfigResource extends AbstractBrooklynRestResource implement
     public Map<String, Object> batchConfigRead(String application, String entityToken, Boolean raw) {
         // TODO: add test
         Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            throw WebResourceUtils.unauthorized("User '%s' is not authorized to see entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+
         // wrap in a task for better runtime view
-        return Entities.submit(entity, Tasks.<Map<String,Object>>builder().displayName("REST API batch config read").body(new BatchConfigRead(this, entity, raw)).build()).getUnchecked();
+        return Entities.submit(entity, Tasks.<Map<String,Object>>builder().displayName("REST API batch config read").body(new BatchConfigRead(mgmt(), this, entity, raw)).build()).getUnchecked();
     }
     
     private static class BatchConfigRead implements Callable<Map<String,Object>> {
-        private EntityConfigResource resource;
-        private Entity entity;
-        private Boolean raw;
+        private final ManagementContext mgmt;
+        private final EntityConfigResource resource;
+        private final Entity entity;
+        private final Boolean raw;
 
-        public BatchConfigRead(EntityConfigResource resource, Entity entity, Boolean raw) {
+        public BatchConfigRead(ManagementContext mgmt, EntityConfigResource resource, Entity entity, Boolean raw) {
+            this.mgmt = mgmt;
             this.resource = resource;
             this.entity = entity;
             this.raw = raw;
@@ -93,9 +110,17 @@ public class EntityConfigResource extends AbstractBrooklynRestResource implement
             Map<ConfigKey<?>, ?> source = ((EntityInternal) entity).config().getBag().getAllConfigAsConfigKeyMap();
             Map<String, Object> result = Maps.newLinkedHashMap();
             for (Map.Entry<ConfigKey<?>, ?> ek : source.entrySet()) {
+                ConfigKey<?> key = ek.getKey();
                 Object value = ek.getValue();
-                result.put(ek.getKey().getName(), 
-                    resource.resolving(value).preferJson(true).asJerseyOutermostReturnValue(false).raw(raw).context(entity).timeout(Duration.ZERO).renderAs(ek.getKey()).resolve());
+                
+                // Exclude config that user is not allowed to see
+                if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.SEE_CONFIG, new EntityAndItem<String>(entity, ek.getKey().getName()))) {
+                    LOG.trace("User {} not authorized to see sensor {} of entity {}; excluding from current-state results", 
+                            new Object[] {Entitlements.getEntitlementContext().user(), ek.getKey().getName(), entity});
+                    continue;
+                }
+                result.put(key.getName(), 
+                    resource.resolving(value).preferJson(true).asJerseyOutermostReturnValue(false).raw(raw).context(entity).timeout(Duration.ZERO).renderAs(key).resolve());
             }
             return result;
         }
@@ -114,6 +139,16 @@ public class EntityConfigResource extends AbstractBrooklynRestResource implement
     public Object get(boolean preferJson, String application, String entityToken, String configKeyName, Boolean raw) {
         Entity entity = brooklyn().getEntity(application, entityToken);
         ConfigKey<?> ck = findConfig(entity, configKeyName);
+        
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            throw WebResourceUtils.unauthorized("User '%s' is not authorized to see entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CONFIG, new EntityAndItem<String>(entity, ck.getName()))) {
+            throw WebResourceUtils.unauthorized("User '%s' is not authorized to see entity '%s' config '%s'",
+                    Entitlements.getEntitlementContext().user(), entity, ck.getName());
+        }
+        
         Object value = ((EntityInternal)entity).config().getRaw(ck).orNull();
         return resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(true).raw(raw).context(entity).timeout(ValueResolver.PRETTY_QUICK_WAIT).renderAs(ck).resolve();
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a0557178/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
index 303c0df..edb5c7f 100644
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
+++ b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
@@ -19,7 +19,6 @@
 package org.apache.brooklyn.rest.resources;
 
 import static com.google.common.collect.Iterables.filter;
-import static com.google.common.collect.Iterables.transform;
 
 import java.util.List;
 import java.util.Map;
@@ -29,6 +28,7 @@ import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.api.sensor.Sensor;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
 import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
 import org.apache.brooklyn.rest.api.SensorApi;
 import org.apache.brooklyn.rest.domain.SensorSummary;
@@ -41,7 +41,6 @@ import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Function;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 
@@ -50,28 +49,49 @@ public class SensorResource extends AbstractBrooklynRestResource implements Sens
 
     private static final Logger log = LoggerFactory.getLogger(SensorResource.class);
 
-    @SuppressWarnings("rawtypes")
     @Override
     public List<SensorSummary> list(final String application, final String entityToken) {
         final Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            throw WebResourceUtils.unauthorized("User '%s' is not authorized to see entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
 
-        return Lists.newArrayList(transform(filter(entity.getEntityType().getSensors(), AttributeSensor.class),
-                new Function<AttributeSensor, SensorSummary>() {
-                    @Override
-                    public SensorSummary apply(AttributeSensor sensor) {
-                        return SensorTransformer.sensorSummary(entity, sensor);
-                    }
-                }));
+        List<SensorSummary> result = Lists.newArrayList();
+        
+        for (AttributeSensor<?> sensor : filter(entity.getEntityType().getSensors(), AttributeSensor.class)) {
+            // Exclude config that user is not allowed to see
+            if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, sensor.getName()))) {
+                log.trace("User {} not authorized to see sensor {} of entity {}; excluding from AttributeSensor list results", 
+                        new Object[] {Entitlements.getEntitlementContext().user(), sensor.getName(), entity});
+                continue;
+            }
+            result.add(SensorTransformer.sensorSummary(entity, sensor));
+        }
+        
+        return result;
     }
 
     @Override
     public Map<String, Object> batchSensorRead(final String application, final String entityToken, final Boolean raw) {
         final Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            throw WebResourceUtils.unauthorized("User '%s' is not authorized to see entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+
         Map<String, Object> sensorMap = Maps.newHashMap();
         @SuppressWarnings("rawtypes")
         Iterable<AttributeSensor> sensors = filter(entity.getEntityType().getSensors(), AttributeSensor.class);
 
         for (AttributeSensor<?> sensor : sensors) {
+            // Exclude sensors that user is not allowed to see
+            if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, sensor.getName()))) {
+                log.trace("User {} not authorized to see sensor {} of entity {}; excluding from current-state results", 
+                        new Object[] {Entitlements.getEntitlementContext().user(), sensor.getName(), entity});
+                continue;
+            }
+
             Object value = entity.getAttribute(findSensor(entity, sensor.getName()));
             sensorMap.put(sensor.getName(), 
                 resolving(value).preferJson(true).asJerseyOutermostReturnValue(false).raw(raw).context(entity).timeout(Duration.ZERO).renderAs(sensor).resolve());
@@ -82,6 +102,16 @@ public class SensorResource extends AbstractBrooklynRestResource implements Sens
     protected Object get(boolean preferJson, String application, String entityToken, String sensorName, Boolean raw) {
         final Entity entity = brooklyn().getEntity(application, entityToken);
         AttributeSensor<?> sensor = findSensor(entity, sensorName);
+        
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            throw WebResourceUtils.unauthorized("User '%s' is not authorized to see entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, sensor.getName()))) {
+            throw WebResourceUtils.unauthorized("User '%s' is not authorized to see entity '%s' sensor '%s'",
+                    Entitlements.getEntitlementContext().user(), entity, sensor.getName());
+        }
+        
         Object value = entity.getAttribute(sensor);
         return resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(true).raw(raw).context(entity).timeout(ValueResolver.PRETTY_QUICK_WAIT).renderAs(sensor).resolve();
     }
@@ -140,6 +170,11 @@ public class SensorResource extends AbstractBrooklynRestResource implements Sens
     @Override
     public void delete(String application, String entityToken, String sensorName) {
         final Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
+            throw WebResourceUtils.unauthorized("User '%s' is not authorized to modify entity '%s'",
+                Entitlements.getEntitlementContext().user(), entity);
+        }
+        
         AttributeSensor<?> sensor = findSensor(entity, sensorName);
         if (log.isDebugEnabled())
             log.debug("REST user "+Entitlements.getEntitlementContext()+" deleting sensor "+sensorName);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a0557178/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AbstractRestApiEntitlementsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AbstractRestApiEntitlementsTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AbstractRestApiEntitlementsTest.java
new file mode 100644
index 0000000..c851b48
--- /dev/null
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AbstractRestApiEntitlementsTest.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.entitlement;
+
+import static org.apache.brooklyn.util.http.HttpTool.httpClientBuilder;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.PerUserEntitlementManager;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncher;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncherTestFixture;
+import org.apache.brooklyn.util.http.HttpAsserts;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.http.HttpToolResponse;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.HttpClient;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+/**
+ * Sets up the REST api with some standard users, ready for testing entitlements.
+ */
+public abstract class AbstractRestApiEntitlementsTest extends BrooklynRestApiLauncherTestFixture {
+
+    protected ManagementContext mgmt;
+    protected TestApplication app;
+    protected TestEntity entity;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        BrooklynProperties props = BrooklynProperties.Factory.newEmpty();
+        props.put(Entitlements.GLOBAL_ENTITLEMENT_MANAGER.getName(), PerUserEntitlementManager.class.getName());
+        props.put(PerUserEntitlementManager.PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".myRoot", "root");
+        props.put(PerUserEntitlementManager.PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".myReadonly", "readonly");
+        props.put(PerUserEntitlementManager.PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".myMinimal", "minimal");
+        props.put(PerUserEntitlementManager.PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".myCustom", StaticDelegatingEntitlementManager.class.getName());
+        
+        mgmt = LocalManagementContextForTests.builder(true).useProperties(props).build();
+        app = mgmt.getEntityManager().createEntity(EntitySpec.create(TestApplication.class)
+                .child(EntitySpec.create(TestEntity.class))
+                        .configure(TestEntity.CONF_NAME, "myname"));
+        entity = (TestEntity) Iterables.getOnlyElement(app.getChildren());
+        
+        useServerForTest(BrooklynRestApiLauncher.launcher()
+                .managementContext(mgmt)
+                .forceUseOfDefaultCatalogWithJavaClassPath(true)
+                .securityProvider(AuthenticateAnyoneSecurityProvider.class)
+                .start());
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (mgmt != null) Entities.destroyAll(mgmt);
+    }
+    
+    protected HttpClient newClient(String user) throws Exception {
+        return httpClientBuilder()
+                .uri(getBaseUri())
+                .credentials(new UsernamePasswordCredentials(user, "ignoredPassword"))
+                .build();
+    }
+
+    protected String httpGet(String user, String path) throws Exception {
+        HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUri()).resolve(path), ImmutableMap.<String, String>of());
+        assertTrue(HttpAsserts.isHealthyStatusCode(response.getResponseCode()), "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase());
+        return response.getContentAsString();
+    }
+    
+    protected String assertPermitted(String user, String path) throws Exception {
+        return httpGet(user, path);
+    }
+
+    protected void assertForbidden(String user, String path) throws Exception {
+        HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUri()).resolve(path), ImmutableMap.<String, String>of());
+        assertEquals(response.getResponseCode(), 401, "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase()+"; content="+response.getContentAsString());
+    }
+
+    protected void assert404(String user, String path) throws Exception {
+        HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUri()).resolve(path), ImmutableMap.<String, String>of());
+        assertEquals(response.getResponseCode(), 404, "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase()+"; content="+response.getContentAsString());
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a0557178/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java
new file mode 100644
index 0000000..b7264b2
--- /dev/null
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.entitlement;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.brooklyn.rest.security.provider.SecurityProvider;
+
+public class AuthenticateAnyoneSecurityProvider implements SecurityProvider {
+
+    @Override
+    public boolean isAuthenticated(HttpSession session) {
+        return false;
+    }
+
+    @Override
+    public boolean authenticate(HttpSession session, String user, String password) {
+        return user != null;
+    }
+
+    @Override
+    public boolean logout(HttpSession session) {
+        return false;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a0557178/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/EntityConfigApiEntitlementsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/EntityConfigApiEntitlementsTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/EntityConfigApiEntitlementsTest.java
new file mode 100644
index 0000000..cbda515
--- /dev/null
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/EntityConfigApiEntitlementsTest.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.entitlement;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementClass;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementManager;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.config.render.RendererHints;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.rest.api.SensorApi;
+import org.apache.brooklyn.rest.resources.SensorResource;
+import org.apache.brooklyn.test.Asserts;
+import org.testng.annotations.Test;
+
+/**
+ * Test the {@link SensorApi} implementation.
+ * <p>
+ * Check that {@link SensorResource} correctly renders {@link AttributeSensor}
+ * values, including {@link RendererHints.DisplayValue} hints.
+ */
+@Test(singleThreaded = true)
+public class EntityConfigApiEntitlementsTest extends AbstractRestApiEntitlementsTest {
+
+    @Test(groups = "Integration")
+    public void testGet() throws Exception {
+        String path = "/v1/applications/"+app.getId()+"/entities/"+entity.getId()+"/config/"+TestEntity.CONF_NAME.getName();
+        String val = "\"myname\"";
+        
+        assertEquals(httpGet("myRoot", path), val);
+        assertEquals(httpGet("myReadonly", path), val);
+        assert404("myMinimal", path); // can't see app, to retrieve entity
+        assert404("unrecognisedUser", path);
+
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveConfig(entity, TestEntity.CONF_NAME.getName()));
+        assertEquals(httpGet("myCustom", path), val);
+        
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveConfig(entity, "differentConfName"));
+        assertForbidden("myCustom", path);
+    }
+    
+    @Test(groups = "Integration")
+    public void testCurrentState() throws Exception {
+        String path = "/v1/applications/"+app.getId()+"/entities/"+entity.getId()+"/config/current-state";
+        String confName = TestEntity.CONF_NAME.getName();
+        String regex = ".*"+confName+".*myname.*";
+        
+        Asserts.assertStringMatchesRegex(httpGet("myRoot", path), regex);
+        Asserts.assertStringMatchesRegex(httpGet("myReadonly", path), regex);
+        assert404("myMinimal", path); // can't see app, to retrieve entity
+        assert404("unrecognisedUser", path);
+
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveConfig(entity, confName));
+        Asserts.assertStringMatchesRegex(httpGet("myCustom", path), regex);
+        
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveConfig(entity, "differentConfName"));
+        String resp = httpGet("myCustom", path);
+        assertFalse(resp.matches(regex), "resp="+resp);
+    }
+    
+    public static class SeeSelectiveConfig implements EntitlementManager {
+        private final Entity entity;
+        private final String regex;
+        
+        public SeeSelectiveConfig(Entity entity, String regex) {
+            this.entity = entity;
+            this.regex = regex;
+        }
+        @Override 
+        @SuppressWarnings("unchecked")
+        public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> entitlementClass, T entitlementClassArgument) {
+            String type = entitlementClass.entitlementClassIdentifier();
+            if (Entitlements.SEE_CONFIG.entitlementClassIdentifier().equals(type)) {
+                EntityAndItem<String> entityAndItem = (EntityAndItem<String>) entitlementClassArgument;
+                return entity.equals(entityAndItem.getEntity()) && entityAndItem.getItem().matches(regex);
+            } else {
+                return true;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a0557178/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/SensorApiEntitlementsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/SensorApiEntitlementsTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/SensorApiEntitlementsTest.java
new file mode 100644
index 0000000..dab72ec
--- /dev/null
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/SensorApiEntitlementsTest.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.entitlement;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementClass;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementManager;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.config.render.RendererHints;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.rest.api.SensorApi;
+import org.apache.brooklyn.rest.resources.SensorResource;
+import org.apache.brooklyn.test.Asserts;
+import org.testng.annotations.Test;
+
+/**
+ * Test the {@link SensorApi} implementation.
+ * <p>
+ * Check that {@link SensorResource} correctly renders {@link AttributeSensor}
+ * values, including {@link RendererHints.DisplayValue} hints.
+ */
+@Test(singleThreaded = true)
+public class SensorApiEntitlementsTest extends AbstractRestApiEntitlementsTest {
+
+    @Test(groups = "Integration")
+    public void testGet() throws Exception {
+        entity.sensors().set(TestEntity.NAME, "myval");
+        
+        String sensorName = TestEntity.NAME.getName();
+        String path = "/v1/applications/"+app.getId()+"/entities/"+entity.getId()+"/sensors/"+sensorName;
+        String val = "\"myval\"";
+        
+        assertEquals(httpGet("myRoot", path), val);
+        assertEquals(httpGet("myReadonly", path), val);
+        assert404("myMinimal", path); // can't see app, to retrieve entity
+        assert404("unrecognisedUser", path);
+
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveSensor(entity, sensorName));
+        assertEquals(httpGet("myCustom", path), val);
+        
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveSensor(entity, "differentConfName"));
+        assertForbidden("myCustom", path);
+    }
+    
+    @Test(groups = "Integration")
+    public void testCurrentState() throws Exception {
+        entity.sensors().set(TestEntity.NAME, "myval");
+        
+        String path = "/v1/applications/"+app.getId()+"/entities/"+entity.getId()+"/sensors/current-state";
+        String sensorName = TestEntity.NAME.getName();
+        String regex = ".*"+sensorName+".*myval.*";
+        
+        Asserts.assertStringMatchesRegex(httpGet("myRoot", path), regex);
+        Asserts.assertStringMatchesRegex(httpGet("myReadonly", path), regex);
+        assert404("myMinimal", path); // can't see app, to retrieve entity
+        assert404("unrecognisedUser", path);
+
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveSensor(entity, sensorName));
+        Asserts.assertStringMatchesRegex(httpGet("myCustom", path), regex);
+        
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveSensor(entity, "differentSensorName"));
+        String resp = httpGet("myCustom", path);
+        assertFalse(resp.matches(regex), "resp="+resp);
+    }
+    
+    public static class SeeSelectiveSensor implements EntitlementManager {
+        private final Entity entity;
+        private final String regex;
+        
+        public SeeSelectiveSensor(Entity entity, String regex) {
+            this.entity = entity;
+            this.regex = regex;
+        }
+        @Override 
+        @SuppressWarnings("unchecked")
+        public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> entitlementClass, T entitlementClassArgument) {
+            String type = entitlementClass.entitlementClassIdentifier();
+            if (Entitlements.SEE_SENSOR.entitlementClassIdentifier().equals(type)) {
+                EntityAndItem<String> entityAndItem = (EntityAndItem<String>) entitlementClassArgument;
+                return entity.equals(entityAndItem.getEntity()) && entityAndItem.getItem().matches(regex);
+            } else {
+                return true;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a0557178/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/ServerApiEntitlementsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/ServerApiEntitlementsTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/ServerApiEntitlementsTest.java
new file mode 100644
index 0000000..afa42cb
--- /dev/null
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/ServerApiEntitlementsTest.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.entitlement;
+
+import org.testng.annotations.Test;
+
+@Test(singleThreaded = true)
+public class ServerApiEntitlementsTest extends AbstractRestApiEntitlementsTest {
+
+    @Test(groups = "Integration")
+    public void testGetHealthy() throws Exception {
+        String path = "/v1/server/up";
+        assertPermitted("myRoot", path);
+        assertForbidden("myReadonly", path);
+        assertForbidden("myMinimal", path);
+        assertForbidden("unrecognisedUser", path);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a0557178/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/StaticDelegatingEntitlementManager.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/StaticDelegatingEntitlementManager.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/StaticDelegatingEntitlementManager.java
new file mode 100644
index 0000000..4370ad6
--- /dev/null
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/StaticDelegatingEntitlementManager.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.entitlement;
+
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementClass;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementManager;
+
+public class StaticDelegatingEntitlementManager implements EntitlementManager {
+
+    private static volatile EntitlementManager delegate;
+    
+    public static void setDelegate(EntitlementManager val) {
+        delegate = val;
+    }
+    
+    @Override
+    public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> entitlementClass, T entitlementClassArgument) {
+        return (delegate != null) ? delegate.isEntitled(context, entitlementClass, entitlementClassArgument) : false;
+    }
+}