Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master 974e7139f -> c563e653a


add per-user entitlement configuration and entitlements documentation


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

Branch: refs/heads/master
Commit: 898aa73ceafa9df4935fd73333ea4cc75a532e47
Parents: c97eaae
Author: Alex Heneveld <[email protected]>
Authored: Mon Dec 22 13:59:36 2014 +0000
Committer: Alex Heneveld <[email protected]>
Committed: Mon Dec 22 18:36:48 2014 +0000

----------------------------------------------------------------------
 .../management/entitlement/Entitlements.java    |  23 +--
 .../entitlement/PerUserEntitlementManager.java  |  97 ++++++++++++
 .../PerUserEntitlementManagerWithDefault.java   |  27 +---
 .../entitlement/AcmeEntitlementManager.java     |  24 ++-
 .../entitlement/AcmeEntitlementManagerTest.java | 114 ++------------
 .../AcmeEntitlementManagerTestFixture.java      | 157 +++++++++++++++++++
 ...PerUserEntitlementManagerPropertiesTest.java |  52 ++++++
 docs/guide/use/guide/management/entitlements.md |  49 ++++++
 docs/guide/use/guide/management/toc.json        |   4 +-
 9 files changed, 406 insertions(+), 141 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/898aa73c/core/src/main/java/brooklyn/management/entitlement/Entitlements.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/brooklyn/management/entitlement/Entitlements.java 
b/core/src/main/java/brooklyn/management/entitlement/Entitlements.java
index a555cac..9772b51 100644
--- a/core/src/main/java/brooklyn/management/entitlement/Entitlements.java
+++ b/core/src/main/java/brooklyn/management/entitlement/Entitlements.java
@@ -18,6 +18,8 @@
  */
 package brooklyn.management.entitlement;
 
+import javax.annotation.Nullable;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -283,25 +285,28 @@ public class Entitlements {
     
     // ----------------- initialization ----------------
 
-    public static ConfigKey<String> GLOBAL_ENTITLEMENT_MANAGER = 
ConfigKeys.newStringConfigKey("brooklyn.entitlements.global", 
+    public final static String ENTITLEMENTS_CONFIG_PREFIX = 
"brooklyn.entitlements";
+    
+    public static ConfigKey<String> GLOBAL_ENTITLEMENT_MANAGER = 
ConfigKeys.newStringConfigKey(ENTITLEMENTS_CONFIG_PREFIX+".global", 
         "Class for entitlements in effect globally; "
         + "short names 'minimal', 'readonly', or 'root' are permitted here, 
with the default 'root' giving full access to all declared users; "
         + "or supply the name of an "+EntitlementManager.class+" class to 
instantiate, taking a 1-arg BrooklynProperties constructor or a 0-arg 
constructor",
         "root");
     
     public static EntitlementManager newManager(ManagementContext mgmt, 
BrooklynProperties brooklynProperties) {
-        EntitlementManager result = newGlobalManager(mgmt, brooklynProperties);
-        // TODO read per user settings from brooklyn.properties, if set there ?
-        return result;
+        return newGlobalManager(mgmt, brooklynProperties);
     }
     private static EntitlementManager newGlobalManager(ManagementContext mgmt, 
BrooklynProperties brooklynProperties) {
-        String type = brooklynProperties.getConfig(GLOBAL_ENTITLEMENT_MANAGER);
-        if ("root".equalsIgnoreCase(type)) return new 
PerUserEntitlementManagerWithDefault(root());
-        if ("readonly".equalsIgnoreCase(type)) return new 
PerUserEntitlementManagerWithDefault(readOnly());
-        if ("minimal".equalsIgnoreCase(type)) return new 
PerUserEntitlementManagerWithDefault(minimal());
+        return load(mgmt, brooklynProperties, 
brooklynProperties.getConfig(GLOBAL_ENTITLEMENT_MANAGER));
+    }
+    
+    public static EntitlementManager load(@Nullable ManagementContext mgmt, 
BrooklynProperties brooklynProperties, String type) {
+        if ("root".equalsIgnoreCase(type)) return root();
+        if ("readonly".equalsIgnoreCase(type) || 
"read_only".equalsIgnoreCase(type)) return readOnly();
+        if ("minimal".equalsIgnoreCase(type)) return minimal();
         if (Strings.isNonBlank(type)) {
             try {
-                ClassLoader cl = 
((ManagementContextInternal)mgmt).getBaseClassLoader();
+                ClassLoader cl = mgmt==null ? null : 
((ManagementContextInternal)mgmt).getBaseClassLoader();
                 if (cl==null) cl = Entitlements.class.getClassLoader();
                 Class<?> clazz = cl.loadClass(type);
                 Optional<?> result = 
Reflections.invokeConstructorWithArgs(clazz, brooklynProperties);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/898aa73c/core/src/main/java/brooklyn/management/entitlement/PerUserEntitlementManager.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/brooklyn/management/entitlement/PerUserEntitlementManager.java
 
b/core/src/main/java/brooklyn/management/entitlement/PerUserEntitlementManager.java
new file mode 100644
index 0000000..5ca0e58
--- /dev/null
+++ 
b/core/src/main/java/brooklyn/management/entitlement/PerUserEntitlementManager.java
@@ -0,0 +1,97 @@
+/*
+ * 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 brooklyn.management.entitlement;
+
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.config.ConfigKey;
+import brooklyn.config.ConfigPredicates;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.text.Strings;
+
+import com.google.common.base.Preconditions;
+
+public class PerUserEntitlementManager implements EntitlementManager {
+
+    private static final Logger log = 
LoggerFactory.getLogger(PerUserEntitlementManager.class);
+    
+    public final static String PER_USER_ENTITLEMENTS_CONFIG_PREFIX = 
Entitlements.ENTITLEMENTS_CONFIG_PREFIX+".perUser";
+    
+    public final static ConfigKey<String> DEFAULT_MANAGER = 
ConfigKeys.newStringConfigKey(PER_USER_ENTITLEMENTS_CONFIG_PREFIX+
+        ".default", "Default entitlements manager for users without further 
specification", "minimal");
+    
+    protected final EntitlementManager defaultManager;
+    protected final Map<String,EntitlementManager> perUserManagers = 
MutableMap.of();
+
+    private final static ThreadLocal<Boolean> ACTIVE = new 
ThreadLocal<Boolean>();
+    
+    private static EntitlementManager load(BrooklynProperties properties, 
String type) {
+        if (Boolean.TRUE.equals(ACTIVE.get())) {
+            // prevent infinite loop
+            throw new IllegalStateException("Cannot set 
"+PerUserEntitlementManager.class.getName()+" within config for itself");
+        }
+        try {
+            ACTIVE.set(true);
+            return Entitlements.load(null, properties, type);
+        } finally {
+            ACTIVE.remove();
+        }
+    }
+    
+    public PerUserEntitlementManager(BrooklynProperties properties) {
+        this(load(properties, properties.getConfig(DEFAULT_MANAGER)));
+        
+        BrooklynProperties users = 
properties.submap(ConfigPredicates.startingWith(PER_USER_ENTITLEMENTS_CONFIG_PREFIX+"."));
+        for (Map.Entry<ConfigKey<?>,?> key: users.getAllConfig().entrySet()) {
+            if (key.getKey().getName().equals(DEFAULT_MANAGER.getName())) 
continue;
+            String user = Strings.removeFromStart(key.getKey().getName(), 
PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".");
+            addUser(user, load(properties, Strings.toString(key.getValue())));
+        }
+        
+        log.info(getClass().getSimpleName()+" created with 
"+perUserManagers.size()+" user"+Strings.s(perUserManagers)+" and "
+            + "default "+defaultManager+" (users: "+perUserManagers+")");
+    }
+    
+    public PerUserEntitlementManager(EntitlementManager defaultManager) {
+        this.defaultManager = Preconditions.checkNotNull(defaultManager);
+    }
+
+    public void addUser(String user, EntitlementManager managerForThisUser) {
+        perUserManagers.put(user, managerForThisUser);
+    }
+
+    @Override
+    public <T> boolean isEntitled(EntitlementContext context, 
EntitlementClass<T> entitlementClass, T entitlementClassArgument) {
+        EntitlementManager entitlementInEffect = null;
+        if (context==null || context.user()==null) {
+            // no user means it is running as an internal process, always has 
root
+            entitlementInEffect = Entitlements.root(); 
+        } else {
+            if (context!=null) entitlementInEffect = 
perUserManagers.get(context.user());
+            if (entitlementInEffect==null) entitlementInEffect = 
defaultManager;
+        }
+        return entitlementInEffect.isEntitled(context, entitlementClass, 
entitlementClassArgument);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/898aa73c/core/src/main/java/brooklyn/management/entitlement/PerUserEntitlementManagerWithDefault.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/brooklyn/management/entitlement/PerUserEntitlementManagerWithDefault.java
 
b/core/src/main/java/brooklyn/management/entitlement/PerUserEntitlementManagerWithDefault.java
index 07ee701..c049b67 100644
--- 
a/core/src/main/java/brooklyn/management/entitlement/PerUserEntitlementManagerWithDefault.java
+++ 
b/core/src/main/java/brooklyn/management/entitlement/PerUserEntitlementManagerWithDefault.java
@@ -18,31 +18,12 @@
  */
 package brooklyn.management.entitlement;
 
-import java.util.Map;
-
-import com.google.common.base.Preconditions;
-
-import brooklyn.util.collections.MutableMap;
-
-public class PerUserEntitlementManagerWithDefault implements 
EntitlementManager {
-
-    protected final EntitlementManager defaultManager;
-    protected final Map<String,EntitlementManager> perUserManagers = 
MutableMap.of();
+@Deprecated
+/** @deprecated since 0.7.0 use {@link PerUserEntitlementManager} */
+public class PerUserEntitlementManagerWithDefault extends 
PerUserEntitlementManager {
 
     public PerUserEntitlementManagerWithDefault(EntitlementManager 
defaultManager) {
-        this.defaultManager = Preconditions.checkNotNull(defaultManager);
+        super(defaultManager);
     }
 
-    public void addUser(String user, EntitlementManager managerForThisUser) {
-        perUserManagers.put(user, managerForThisUser);
-    }
-
-    @Override
-    public <T> boolean isEntitled(EntitlementContext context, 
EntitlementClass<T> entitlementClass, T entitlementClassArgument) {
-        EntitlementManager entitlementInEffect = null;
-        if (context!=null) entitlementInEffect = 
perUserManagers.get(context.user());
-        if (entitlementInEffect==null) entitlementInEffect = defaultManager; 
-        return entitlementInEffect.isEntitled(context, entitlementClass, 
entitlementClassArgument);
-    }
-    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/898aa73c/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManager.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManager.java
 
b/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManager.java
index 706d5bf..9cc02dd 100644
--- 
a/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManager.java
+++ 
b/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManager.java
@@ -18,13 +18,29 @@
  */
 package brooklyn.management.entitlement;
 
-class AcmeEntitlementManager extends PerUserEntitlementManagerWithDefault {
+class AcmeEntitlementManager extends PerUserEntitlementManager {
 
     public AcmeEntitlementManager() {
+        // default mode (if no user specified) is root
         super(Entitlements.root());
-        super.addUser("hacker", Entitlements.minimal());
-        super.addUser("bob", Entitlements.readOnly());
-        super.addUser("alice", Entitlements.root());
+        
+        super.addUser("admin", Entitlements.root());
+        
+        super.addUser("support", Entitlements.readOnly());
+        
+        // metrics can log in but can't really do much else
+        super.addUser("metrics", Entitlements.minimal());
+        
+        // 'navigator' defines a user with a custom entitlement manager 
allowing 
+        // access to see entities (in the tree) but not to do anything 
+        // or even see any sensor information on those entities
+        super.addUser("navigator", new EntitlementManager() {
+            @Override
+            public <T> boolean isEntitled(EntitlementContext context, 
EntitlementClass<T> entitlementClass, T entitlementClassArgument) {
+                if (Entitlements.SEE_ENTITY.equals(entitlementClass)) return 
true;
+                return false;
+            }
+        });
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/898aa73c/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManagerTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManagerTest.java
 
b/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManagerTest.java
index f5575b4..7a34351 100644
--- 
a/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManagerTest.java
+++ 
b/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManagerTest.java
@@ -21,132 +21,38 @@ package brooklyn.management.entitlement;
 import java.net.URI;
 
 import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.google.common.collect.ImmutableList;
-
-import brooklyn.config.BrooklynProperties;
-import brooklyn.entity.Application;
-import brooklyn.entity.basic.ApplicationBuilder;
-import brooklyn.entity.basic.BasicApplication;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.location.Location;
-import brooklyn.management.ManagementContext;
 import brooklyn.management.entitlement.Entitlements.EntityAndItem;
-import brooklyn.test.entity.LocalManagementContextForTests;
-import brooklyn.util.config.ConfigBag;
-import brooklyn.util.exceptions.Exceptions;
-
-public class AcmeEntitlementManagerTest {
 
-    ManagementContext mgmt;
-    Application app;
-    ConfigBag configBag;
-    
-    public void setup(ConfigBag cfg) {
-        mgmt = new 
LocalManagementContextForTests(BrooklynProperties.Factory.newEmpty().addFrom(cfg));
-        app = 
ApplicationBuilder.newManagedApp(EntitySpec.create(BasicApplication.class), 
mgmt);
-    }
+public class AcmeEntitlementManagerTest extends 
AcmeEntitlementManagerTestFixture {
 
-    @BeforeMethod(alwaysRun=true)
-    public void init() {
-        Entitlements.clearEntitlementContext();
-        configBag = ConfigBag.newInstance();
+    protected void addGlobalConfig() {
         configBag.put(Entitlements.GLOBAL_ENTITLEMENT_MANAGER, 
AcmeEntitlementManager.class.getName());
     }
 
-    @AfterMethod(alwaysRun=true)
-    public void tearDown() {
-        Entitlements.clearEntitlementContext();
-        if (app != null) Entities.destroyAll(app.getManagementContext());
-        if (mgmt != null) Entities.destroyAll(mgmt);
-        app = null;
-        mgmt = null;
+    @Test
+    public void testAnonUserHasAllPermissions() {
+        checkUserHasAllPermissions("anon");
     }
 
     @Test
-    public void testUserWithMinimal() {
-        setup(configBag);
-        WebEntitlementContext entitlementContext = new 
WebEntitlementContext("hacker", "127.0.0.1", 
URI.create("/applications").toString(), "H");
-        Entitlements.setEntitlementContext(entitlementContext);
-        
Assert.assertFalse(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.ROOT, null));
-        
Assert.assertFalse(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_ENTITY, app));
-        
Assert.assertFalse(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.INVOKE_EFFECTOR, EntityAndItem.of(app, "any-eff")));
-        
Assert.assertFalse(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_SENSOR, EntityAndItem.of(app, "any-sensor")));
-        // and can invoke methods
-        confirmEffectorEntitlement(false);
+    public void testNullUserHasAllPermissions() {
+        checkUserHasAllPermissions(null);
     }
 
     @Test
-    public void testUserWithReadOnly() {
+    public void testNavigatorHasListPermissionsOnly() {
         setup(configBag);
-        WebEntitlementContext entitlementContext = new 
WebEntitlementContext("bob", "127.0.0.1", 
URI.create("/applications").toString(), "B");
+        WebEntitlementContext entitlementContext = new 
WebEntitlementContext("navigator", "127.0.0.1", URI.create("/X").toString(), 
"X");
         Entitlements.setEntitlementContext(entitlementContext);
         
Assert.assertFalse(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.ROOT, null));
         
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_ENTITY, app));
         
Assert.assertFalse(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.INVOKE_EFFECTOR, EntityAndItem.of(app, "any-eff")));
-        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_SENSOR, EntityAndItem.of(app, "any-sensor")));
+        
Assert.assertFalse(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_SENSOR, EntityAndItem.of(app, "any-sensor")));
         // and cannot invoke methods
         confirmEffectorEntitlement(false);
     }
 
-    @Test
-    public void testUserWithAllPermissions() {
-        setup(configBag);
-        WebEntitlementContext entitlementContext = new 
WebEntitlementContext("alice", "127.0.0.1", 
URI.create("/applications").toString(), "A");
-        Entitlements.setEntitlementContext(entitlementContext);
-        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.ROOT, null));
-        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_ENTITY, app));
-        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.INVOKE_EFFECTOR, EntityAndItem.of(app, "any-eff")));
-        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_SENSOR, EntityAndItem.of(app, "any-sensor")));
-        // and can invoke methods
-        confirmEffectorEntitlement(true);
-    }
-
-    @Test
-    public void testNullHasAllPermissions() {
-        setup(configBag);
-        WebEntitlementContext entitlementContext = new 
WebEntitlementContext(null, "127.0.0.1", 
URI.create("/applications").toString(), "X");
-        Entitlements.setEntitlementContext(entitlementContext);
-        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.ROOT, null));
-        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_ENTITY, app));
-        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.INVOKE_EFFECTOR, EntityAndItem.of(app, "any-eff")));
-        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_SENSOR, EntityAndItem.of(app, "any-sensor")));
-        // and can invoke methods
-        confirmEffectorEntitlement(true);
-    }
-
-    protected void confirmEffectorEntitlement(boolean shouldSucceed) {
-        try {
-            ((BasicApplication)app).start(ImmutableList.<Location>of());
-            checkNoException(shouldSucceed);
-        } catch (Exception e) {
-            checkNotEntitledException(shouldSucceed, e);
-        }
-    }
-
-    private void checkNoException(boolean shouldBeEntitled) {
-        checkNotEntitledException(shouldBeEntitled, null);
-    }
-
-    private void checkNotEntitledException(boolean shouldBeEntitled, Exception 
e) {
-        if (e==null) {
-            if (shouldBeEntitled) return;
-            Assert.fail("entitlement should have been denied");
-        }
-        
-        Exception notEntitled = Exceptions.getFirstThrowableOfType(e, 
NotEntitledException.class);
-        if (notEntitled==null)
-            throw Exceptions.propagate(e);
-        if (!shouldBeEntitled) {
-            /* denied, as it should have been */ 
-            return;
-        }
-        Assert.fail("entitlement should have been granted, but was denied: 
"+e);
-    }
-
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/898aa73c/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManagerTestFixture.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManagerTestFixture.java
 
b/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManagerTestFixture.java
new file mode 100644
index 0000000..24f1e52
--- /dev/null
+++ 
b/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManagerTestFixture.java
@@ -0,0 +1,157 @@
+/*
+ * 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 brooklyn.management.entitlement;
+
+import java.io.IOException;
+import java.net.URI;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.entity.Application;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.BasicApplication;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.entitlement.Entitlements;
+import brooklyn.management.entitlement.NotEntitledException;
+import brooklyn.management.entitlement.WebEntitlementContext;
+import brooklyn.management.entitlement.Entitlements.EntityAndItem;
+import brooklyn.test.entity.LocalManagementContextForTests;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.exceptions.Exceptions;
+
+public abstract class AcmeEntitlementManagerTestFixture {
+
+    ManagementContext mgmt;
+    Application app;
+    ConfigBag configBag;
+    
+    public void setup(ConfigBag cfg) {
+        mgmt = new 
LocalManagementContextForTests(BrooklynProperties.Factory.newEmpty().addFrom(cfg));
+        app = 
ApplicationBuilder.newManagedApp(EntitySpec.create(BasicApplication.class), 
mgmt);
+    }
+
+    @BeforeMethod(alwaysRun=true)
+    public void init() throws IOException {
+        Entitlements.clearEntitlementContext();
+        configBag = ConfigBag.newInstance();
+        addGlobalConfig();
+    }
+
+    protected abstract void addGlobalConfig() throws IOException;
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() {
+        Entitlements.clearEntitlementContext();
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+        if (mgmt != null) Entities.destroyAll(mgmt);
+        app = null;
+        mgmt = null;
+    }
+
+    @Test
+    public void testMetricsHasMinimalPermissions() {
+        checkUserHasMinimalPermissions("metrics");
+    }
+    
+    public void checkUserHasMinimalPermissions(String username) {
+        setup(configBag);
+        WebEntitlementContext entitlementContext = new 
WebEntitlementContext(username, "127.0.0.1", 
URI.create("/applications").toString(), "H");
+        Entitlements.setEntitlementContext(entitlementContext);
+        
Assert.assertFalse(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.ROOT, null));
+        
Assert.assertFalse(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_ENTITY, app));
+        
Assert.assertFalse(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.INVOKE_EFFECTOR, EntityAndItem.of(app, "any-eff")));
+        
Assert.assertFalse(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_SENSOR, EntityAndItem.of(app, "any-sensor")));
+        // and can invoke methods
+        confirmEffectorEntitlement(false);
+    }
+
+    @Test
+    public void testSupportHasReadOnlyPermissions() {
+        checkUserHasReadOnlyPermissions("support");
+    }
+    
+    public void checkUserHasReadOnlyPermissions(String username) {
+        setup(configBag);
+        WebEntitlementContext entitlementContext = new 
WebEntitlementContext(username, "127.0.0.1", URI.create("/X").toString(), "B");
+        Entitlements.setEntitlementContext(entitlementContext);
+        
Assert.assertFalse(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.ROOT, null));
+        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_ENTITY, app));
+        
Assert.assertFalse(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.INVOKE_EFFECTOR, EntityAndItem.of(app, "any-eff")));
+        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_SENSOR, EntityAndItem.of(app, "any-sensor")));
+        // and cannot invoke methods
+        confirmEffectorEntitlement(false);
+    }
+
+    @Test
+    public void testAdminHasAllPermissions() {
+        checkUserHasAllPermissions("admin");
+    }
+    
+    public void checkUserHasAllPermissions(String user) {
+        setup(configBag);
+        WebEntitlementContext entitlementContext = new 
WebEntitlementContext(user, "127.0.0.1", URI.create("/X").toString(), "A");
+        Entitlements.setEntitlementContext(entitlementContext);
+        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.ROOT, null));
+        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_ENTITY, app));
+        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.INVOKE_EFFECTOR, EntityAndItem.of(app, "any-eff")));
+        
Assert.assertTrue(Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_SENSOR, EntityAndItem.of(app, "any-sensor")));
+        // and can invoke methods
+        confirmEffectorEntitlement(true);
+    }
+
+    protected void confirmEffectorEntitlement(boolean shouldSucceed) {
+        try {
+            ((BasicApplication)app).start(ImmutableList.<Location>of());
+            checkNoException(shouldSucceed);
+        } catch (Exception e) {
+            checkNotEntitledException(shouldSucceed, e);
+        }
+    }
+
+    private void checkNoException(boolean shouldBeEntitled) {
+        checkNotEntitledException(shouldBeEntitled, null);
+    }
+
+    private void checkNotEntitledException(boolean shouldBeEntitled, Exception 
e) {
+        if (e==null) {
+            if (shouldBeEntitled) return;
+            Assert.fail("entitlement should have been denied");
+        }
+        
+        Exception notEntitled = Exceptions.getFirstThrowableOfType(e, 
NotEntitledException.class);
+        if (notEntitled==null)
+            throw Exceptions.propagate(e);
+        if (!shouldBeEntitled) {
+            /* denied, as it should have been */ 
+            return;
+        }
+        Assert.fail("entitlement should have been granted, but was denied: 
"+e);
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/898aa73c/core/src/test/java/brooklyn/management/entitlement/PerUserEntitlementManagerPropertiesTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/brooklyn/management/entitlement/PerUserEntitlementManagerPropertiesTest.java
 
b/core/src/test/java/brooklyn/management/entitlement/PerUserEntitlementManagerPropertiesTest.java
new file mode 100644
index 0000000..63e2f62
--- /dev/null
+++ 
b/core/src/test/java/brooklyn/management/entitlement/PerUserEntitlementManagerPropertiesTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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 brooklyn.management.entitlement;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Properties;
+
+import org.testng.annotations.Test;
+
+
+public class PerUserEntitlementManagerPropertiesTest extends 
AcmeEntitlementManagerTestFixture {
+
+    protected void addGlobalConfig() throws IOException {
+        Properties moreProps = new Properties();
+        moreProps.load(new StringReader(
+            
"brooklyn.entitlements.global=brooklyn.management.entitlement.PerUserEntitlementManager\n"
+                + "brooklyn.entitlements.perUser.admin=root\n"
+                + "brooklyn.entitlements.perUser.support=readonly\n"
+                + "brooklyn.entitlements.perUser.metrics=minimal"));
+        configBag.putAll(moreProps);
+    }
+    
+    @Test
+    public void testAnonUserHasMinimalPermissions() {
+        checkUserHasMinimalPermissions("anon");
+    }
+
+    @Test
+    public void testNullUserHasAllPermissions() {
+        checkUserHasAllPermissions(null);
+    }
+
+
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/898aa73c/docs/guide/use/guide/management/entitlements.md
----------------------------------------------------------------------
diff --git a/docs/guide/use/guide/management/entitlements.md 
b/docs/guide/use/guide/management/entitlements.md
new file mode 100644
index 0000000..c3f6083
--- /dev/null
+++ b/docs/guide/use/guide/management/entitlements.md
@@ -0,0 +1,49 @@
+---
+title: Entitlements
+layout: guide-normal
+toc: ../guide_toc.json
+categories: [use, guide]
+---
+
+Brooklyn supports a plug-in system for defining ``entitlements'' -- 
+essentially permissions.
+
+Any entitlement scheme can be implemented by supplying a class which 
implements one method on one class:
+
+    public interface EntitlementManager {
+      public <T> boolean isEntitled(@Nullable EntitlementContext context, 
@Nonnull EntitlementClass<T> entitlementClass, @Nullable T 
entitlementClassArgument);
+    }
+
+This answers the question who is allowed do what to whom, looking at the 
following fields:
+
+* `context`: the user who is logged in and is attempting an action
+  (extensions can contain additional metadata)
+* `entitlementClass`: the type of action being queried, e.g. 
`DEPLOY_APPLICATION` or `SEE_SENSOR`
+  (declared in the class `Entitlements`)
+* `entitlementClassArgument`: details of the action being queried,
+  such as the blueprint in the case of `DEPLOY_APPLICATION` or the entity and 
sensor name in the case
+  of `SEE_SENSOR`
+
+To set a custom entitlements manager to apply across the board, simply use:
+
+    
brooklyn.entitlements.global=brooklyn.management.entitlement.AcmeEntitlementManager
+
+The example above refers to a sample manager which is included in the test 
JARs of Brooklyn,
+which you can see [here]({{ site.brooklyn.url.git 
}}/core/src/test/java/brooklyn/management/entitlement/AcmeEntitlementManagerTest.java),
+and include in your project by adding the core tests JAR to your `dropins` 
folder.
+
+
+## Please Make it Simpler
+
+There are some entitlements schemes which exist out of the box,
+and you can use them simply by editing your `brooklyn.properties`:
+
+    
brooklyn.entitlements.global=brooklyn.management.entitlement.PerUserEntitlementManager
+    brooklyn.entitlements.perUser.admin=root
+    brooklyn.entitlements.perUser.support=readonly
+    brooklyn.entitlements.perUser.metrics=minimal
+
+
+Some users have gone further and build LDAP extensions which re-use the LDAP 
authorization support
+in Brooklyn, allowing permissions objects to be declared in LDAP and used to 
make entitlement decisions.
+For more information on this, ask on IRC or the mailing list.

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/898aa73c/docs/guide/use/guide/management/toc.json
----------------------------------------------------------------------
diff --git a/docs/guide/use/guide/management/toc.json 
b/docs/guide/use/guide/management/toc.json
index e30d160..b8d5ae4 100644
--- a/docs/guide/use/guide/management/toc.json
+++ b/docs/guide/use/guide/management/toc.json
@@ -15,4 +15,6 @@
 { "title": "Key APIs",
   "file":  "{{ site.path.guide }}/use/guide/management/index.html#key-apis" }, 
 { "title": "Sensors and Effectors",
-  "file":  "{{ site.path.guide 
}}/use/guide/management/index.html#sensors-and-effectors" }]
+  "file":  "{{ site.path.guide 
}}/use/guide/management/index.html#sensors-and-effectors" },
+{ "title": "Entitlements",
+  "file":  "{{ site.path.guide }}/use/guide/management/entitlements.html" }]

Reply via email to