From 235e0c3bf51c22e18e5a4add05505e59dc3748cb Mon Sep 17 00:00:00 2001 From: Sam Corbett Date: Thu, 7 Apr 2016 15:49:59 +0100 Subject: [PATCH 1/3] Add SEE_CATALOG_ITEM to readOnly entitlement group --- .../org/apache/brooklyn/core/mgmt/entitlement/Entitlements.java | 1 + 1 file changed, 1 insertion(+) 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 c5b62a49a4..6d2c85c165 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 @@ -329,6 +329,7 @@ public static EntitlementManager readOnly() { return FineGrainedEntitlements.anyOf( FineGrainedEntitlements.allowing(SEE_ENTITY), FineGrainedEntitlements.allowing(SEE_ACTIVITY_STREAMS), + FineGrainedEntitlements.allowing(SEE_CATALOG_ITEM), FineGrainedEntitlements.seeNonSecretSensors(), FineGrainedEntitlements.seeNonSecretConfig() ); From 27dacc3e49abdd6b924a497901fe85ddd6a303fd Mon Sep 17 00:00:00 2001 From: Sam Corbett Date: Thu, 7 Apr 2016 15:53:54 +0100 Subject: [PATCH 2/3] Adds a new entitlement group for regular access It is intended to disallow access to destructive server methods, thus forbids ROOT and SEE_ALL_SERVER_INFO classes. --- .../EntitlementManagerAdapter.java | 4 + .../core/mgmt/entitlement/Entitlements.java | 116 +++++++++++------- .../PerUserEntitlementManager.java | 6 +- .../apache/brooklyn/rest/api/ScriptApi.java | 8 +- .../AbstractRestApiEntitlementsTest.java | 35 +++++- .../ActivityApiEntitlementsTest.java | 2 + .../EntityConfigApiEntitlementsTest.java | 2 + .../SensorApiEntitlementsTest.java | 2 + 8 files changed, 123 insertions(+), 52 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/EntitlementManagerAdapter.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/EntitlementManagerAdapter.java index b722a006a7..57c6d3becd 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/EntitlementManagerAdapter.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/EntitlementManagerAdapter.java @@ -53,10 +53,12 @@ protected Handler(EntitlementContext context) { public Boolean handleSeeCatalogItem(String catalogItemId) { return isEntitledToSeeCatalogItem(context, catalogItemId); } + @Override public Boolean handleAddCatalogItem(Object catalogItemBeingAdded) { return isEntitledToAddCatalogItem(context, catalogItemBeingAdded); } + @Override public Boolean handleModifyCatalogItem(StringAndArgument catalogItemIdAndModification) { return isEntitledToModifyCatalogItem(context, catalogItemIdAndModification==null ? null : catalogItemIdAndModification.getString(), @@ -67,6 +69,7 @@ public Boolean handleModifyCatalogItem(StringAndArgument catalogItemIdAndModific public Boolean handleSeeEntity(Entity entity) { return isEntitledToSeeEntity(context, entity); } + @Override public Boolean handleSeeSensor(EntityAndItem sensorInfo) { return isEntitledToSeeSensor(context, sensorInfo.getEntity(), sensorInfo.getItem()); @@ -76,6 +79,7 @@ public Boolean handleInvokeEffector(EntityAndItem effectorInf StringAndArgument item = effectorInfo.getItem(); return isEntitledToInvokeEffector(context, effectorInfo.getEntity(), item==null ? null : item.getString(), item==null ? null : item.getArgument()); } + @Override public Boolean handleModifyEntity(Entity entity) { return isEntitledToModifyEntity(context, entity); 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 6d2c85c165..7199267359 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 @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.List; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.brooklyn.api.entity.Entity; @@ -72,23 +73,32 @@ public class Entitlements { public static EntitlementClass> INVOKE_EFFECTOR = new BasicEntitlementClassDefinition>("effector.invoke", EntityAndItem.typeToken(StringAndArgument.class)); public static EntitlementClass MODIFY_ENTITY = new BasicEntitlementClassDefinition("entity.modify", Entity.class); - /** the permission to deploy an application, where parameter is some representation of the app to be deployed (spec instance or yaml plan) */ + /** + * Permission to deploy an application, where parameter is some representation + * of the app to be deployed (spec instance or yaml plan) + */ public static EntitlementClass DEPLOY_APPLICATION = new BasicEntitlementClassDefinition("app.deploy", Object.class); - /** catch-all for catalog, locations, scripting, usage, etc - exporting persistence, shutting down, etc; + /** + * Catch-all for catalog, locations, scripting, usage, etc - exporting persistence, shutting down, etc; * this is significantly more powerful than {@link #SERVER_STATUS}. - * NB: this may be refactored and deprecated in future */ + * NB: this may be refactored and deprecated in future + */ public static EntitlementClass SEE_ALL_SERVER_INFO = new BasicEntitlementClassDefinition("server.info.all.see", Void.class); - /** permission to see general server status info: basically HA status; not nearly as much as {@link #SEE_ALL_SERVER_INFO} */ + /** + * Permission to see general server status info: basically HA status; not nearly as much as {@link #SEE_ALL_SERVER_INFO} + */ public static EntitlementClass SERVER_STATUS = new BasicEntitlementClassDefinition("server.status", Void.class); - /** permission to run untrusted code or embedded scripts at the server; - * secondary check required for any operation which could potentially grant root-level access */ + /** + * Permission to run untrusted code or embedded scripts at the server. + * A secondary check is required for any operation which could potentially grant root-level access. + */ public static EntitlementClass ROOT = new BasicEntitlementClassDefinition("root", Void.class); @SuppressWarnings("unchecked") - public static enum EntitlementClassesEnum { + public enum EntitlementClassesEnum { ENTITLEMENT_SEE_CATALOG_ITEM(SEE_CATALOG_ITEM) { public T handle(EntitlementClassesHandler handler, Object argument) { return handler.handleSeeCatalogItem((String)argument); } }, ENTITLEMENT_ADD_CATALOG_ITEM(ADD_CATALOG_ITEM) { public T handle(EntitlementClassesHandler handler, Object argument) { return handler.handleAddCatalogItem(argument); } }, ENTITLEMENT_MODIFY_CATALOG_ITEM(MODIFY_CATALOG_ITEM) { public T handle(EntitlementClassesHandler handler, Object argument) { return handler.handleModifyCatalogItem((StringAndArgument)argument); } }, @@ -107,7 +117,7 @@ public static enum EntitlementClassesEnum { private EntitlementClass entitlementClass; - private EntitlementClassesEnum(EntitlementClass specificClass) { + EntitlementClassesEnum(EntitlementClass specificClass) { this.entitlementClass = specificClass; } public EntitlementClass getEntitlementClass() { @@ -194,7 +204,9 @@ public static class LifecycleEffectors { // ------------- permission sets ------------- - /** always ALLOW access to everything */ + /** + * @return An entitlement manager allowing access to everything. + */ public static EntitlementManager root() { return new EntitlementManager() { @Override @@ -208,7 +220,25 @@ public String toString() { }; } - /** always DENY access to anything which requires entitlements */ + /** + * @return An entitlement manager allowing everything but {@link #ROOT} and {@link #SEE_ALL_SERVER_INFO}. + */ + public static EntitlementManager user() { + return new EntitlementManager() { + @Override + public boolean isEntitled(EntitlementContext context, EntitlementClass permission, T entitlementClassArgument) { + return !SEE_ALL_SERVER_INFO.equals(permission) && !ROOT.equals(permission); + } + @Override + public String toString() { + return "Entitlements.user"; + } + }; + } + + /** + * @return An entitlement manager denying access to anything that requires entitlements. + */ public static EntitlementManager minimal() { return new EntitlementManager() { @Override @@ -286,41 +316,32 @@ protected SinglePermissionEntitlementChecker(EntitlementClass permission, Pre @SuppressWarnings("unchecked") @Override public boolean isEntitled(EntitlementContext context, EntitlementClass permission, T typeArgument) { - if (!Objects.equal(this.permission, permission)) return false; - return test.apply((U)typeArgument); + return Objects.equal(this.permission, permission) && test.apply((U) typeArgument); } @Override public String toString() { return "Entitlements.allowing(" + permission + " -> " + test + ")"; } } - + + private static class NonSecretPredicate implements Predicate> { + @Override + public boolean apply(EntityAndItem input) { + return input != null && !Sanitizer.IS_SECRET_PREDICATE.apply(input.getItem()); + } + + @Override + public String toString() { + return "Predicates.nonSecret"; + } + } + public static EntitlementManager seeNonSecretSensors() { - return allowing(SEE_SENSOR, new Predicate>() { - @Override - public boolean apply(EntityAndItem input) { - if (input == null) return false; - return !Sanitizer.IS_SECRET_PREDICATE.apply(input.getItem()); - } - @Override - public String toString() { - return "Predicates.nonSecret"; - } - }); + return allowing(SEE_SENSOR, new NonSecretPredicate()); } public static EntitlementManager seeNonSecretConfig() { - return allowing(SEE_CONFIG, new Predicate>() { - @Override - public boolean apply(EntityAndItem input) { - if (input == null) return false; - return !Sanitizer.IS_SECRET_PREDICATE.apply(input.getItem()); - } - @Override - public String toString() { - return "Predicates.nonSecret"; - } - }); + return allowing(SEE_CONFIG, new NonSecretPredicate()); } } @@ -399,6 +420,8 @@ public static void checkEntitled(EntitlementManager checker, EntitlementClas * @since 0.7.0 * @deprecated since 0.7.0, use {@link #checkEntitled(EntitlementManager, EntitlementClass, Object)}; * kept briefly because there is some downstream usage*/ + // Note: @Deprecated annotation only added from v0.10.0. + @Deprecated public static void requireEntitled(EntitlementManager checker, EntitlementClass permission, T typeArgument) { checkEntitled(checker, permission, typeArgument); } @@ -406,10 +429,10 @@ public static void requireEntitled(EntitlementManager checker, EntitlementCl // ----------------- initialization ---------------- public final static String ENTITLEMENTS_CONFIG_PREFIX = "brooklyn.entitlements"; - - public static ConfigKey GLOBAL_ENTITLEMENT_MANAGER = ConfigKeys.newStringConfigKey(ENTITLEMENTS_CONFIG_PREFIX+".global", + + public static final ConfigKey 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; " + + "short names 'minimal', 'readonly', 'user' 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"); @@ -421,13 +444,20 @@ private static EntitlementManager newGlobalManager(ManagementContext mgmt, Brook } 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 ("root".equalsIgnoreCase(type)) { + return root(); + } else if ("readonly".equalsIgnoreCase(type) || "read_only".equalsIgnoreCase(type)) { + return readOnly(); + } else if ("minimal".equalsIgnoreCase(type)) { + return minimal(); + } else if ("user".equalsIgnoreCase(type)) { + return user(); + } if (Strings.isNonBlank(type)) { try { - ClassLoader cl = mgmt==null ? null : ((ManagementContextInternal)mgmt).getCatalogClassLoader(); - if (cl==null) cl = Entitlements.class.getClassLoader(); + ClassLoader cl = mgmt != null + ? mgmt.getCatalogClassLoader() + : Entitlements.class.getClassLoader(); Class clazz = cl.loadClass(DeserializingClassRenamesProvider.findMappedName(type)); return (EntitlementManager) instantiate(clazz, ImmutableList.of( new Object[] {mgmt, brooklynProperties}, diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/PerUserEntitlementManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/PerUserEntitlementManager.java index dd0b1ba469..5452060c2a 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/PerUserEntitlementManager.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/entitlement/PerUserEntitlementManager.java @@ -64,7 +64,7 @@ private static EntitlementManager load(BrooklynProperties properties, String typ public PerUserEntitlementManager(BrooklynProperties properties) { this(load(properties, properties.getConfig(DEFAULT_MANAGER))); - BrooklynProperties users = properties.submap(ConfigPredicates.startingWith(PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".")); + BrooklynProperties users = properties.submap(ConfigPredicates.nameStartsWith(PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".")); for (Map.Entry,?> 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+"."); @@ -85,12 +85,12 @@ public void addUser(String user, EntitlementManager managerForThisUser) { @Override public boolean isEntitled(EntitlementContext context, EntitlementClass entitlementClass, T entitlementClassArgument) { - EntitlementManager entitlementInEffect = null; + EntitlementManager entitlementInEffect; 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()); + entitlementInEffect = perUserManagers.get(context.user()); if (entitlementInEffect==null) entitlementInEffect = defaultManager; } return entitlementInEffect.isEntitled(context, entitlementClass, entitlementClassArgument); diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ScriptApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ScriptApi.java index 72af2c30eb..a50bf96fb0 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ScriptApi.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ScriptApi.java @@ -36,10 +36,14 @@ @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public interface ScriptApi { - + + /** @deprecated since 0.10.0. Use constant in ScriptResource instead. */ + @Deprecated public static final String USER_DATA_MAP_SESSION_ATTRIBUTE = "brooklyn.script.groovy.user.data"; + /** @deprecated since 0.10.0. Use constant in ScriptResource instead. */ + @Deprecated public static final String USER_LAST_VALUE_SESSION_ATTRIBUTE = "brooklyn.script.groovy.user.last"; - + @POST @Path("/groovy") @Consumes("application/text") 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 index 7331b395ec..4a0d568271 100644 --- 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 @@ -62,6 +62,7 @@ public void setUp() throws Exception { 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+".myUser", "user"); props.put(PerUserEntitlementManager.PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".myCustom", StaticDelegatingEntitlementManager.class.getName()); mgmt = LocalManagementContextForTests.builder(true).useProperties(props).build(); @@ -91,21 +92,47 @@ protected HttpClient newClient(String user) throws Exception { protected String httpGet(String user, String path) throws Exception { HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUriRest()).resolve(path), ImmutableMap.of()); - assertTrue(HttpAsserts.isHealthyStatusCode(response.getResponseCode()), "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase()); + assertHealthyStatusCode(response); return response.getContentAsString(); } - + + protected HttpToolResponse httpPost(String user, String path, byte[] body) throws Exception { + final ImmutableMap headers = ImmutableMap.of(); + final URI uri = URI.create(getBaseUriRest()).resolve(path); + return HttpTool.httpPost(newClient(user), uri, headers, body); + } + protected String assertPermitted(String user, String path) throws Exception { return httpGet(user, path); } + public void assertPermittedPost(String user, String path, byte[] body) throws Exception { + HttpToolResponse response = httpPost(user, path, body); + assertHealthyStatusCode(response); + } + + protected void assertHealthyStatusCode(HttpToolResponse response) { + assertTrue(HttpAsserts.isHealthyStatusCode(response.getResponseCode()), "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase()); + } + protected void assertForbidden(String user, String path) throws Exception { HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUriRest()).resolve(path), ImmutableMap.of()); - assertEquals(response.getResponseCode(), 403, "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase()+"; content="+response.getContentAsString()); + assertStatusCodeEquals(response, 403); + } + + public void assertForbiddenPost(String user, String path, byte[] body) throws Exception { + HttpToolResponse response = httpPost(user, path, body); + assertEquals(response.getResponseCode(), 403, "code=" + response.getResponseCode() + "; reason=" + response.getReasonPhrase()); } protected void assert404(String user, String path) throws Exception { HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUriRest()).resolve(path), ImmutableMap.of()); - assertEquals(response.getResponseCode(), 404, "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase()+"; content="+response.getContentAsString()); + assertStatusCodeEquals(response, 404); } + + protected void assertStatusCodeEquals(HttpToolResponse response, int expected) { + assertEquals(response.getResponseCode(), expected, + "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase()+"; content="+response.getContentAsString()); + } + } diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/ActivityApiEntitlementsTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/ActivityApiEntitlementsTest.java index 2f1cc7bf71..4a7a0b3a79 100644 --- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/ActivityApiEntitlementsTest.java +++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/ActivityApiEntitlementsTest.java @@ -76,6 +76,7 @@ public void setUp() throws Exception { public void testGetTask() throws Exception { String path = "/v1/activities/"+subTask.getId(); assertPermitted("myRoot", path); + assertPermitted("myUser", path); assertPermitted("myReadonly", path); assertForbidden("myMinimal", path); assertForbidden("unrecognisedUser", path); @@ -89,6 +90,7 @@ public void testGetStream() throws Exception { String expectedStream = entry.getValue(); assertEquals(httpGet("myRoot", pathPrefix+streamId), expectedStream); + assertEquals(httpGet("myUser", pathPrefix+streamId), expectedStream); assertEquals(httpGet("myReadonly", pathPrefix+streamId), expectedStream); assertForbidden("myMinimal", pathPrefix+streamId); assertForbidden("unrecognisedUser", pathPrefix+streamId); 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 index cbda515a5c..b95392b99b 100644 --- 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 @@ -50,6 +50,7 @@ public void testGet() throws Exception { String val = "\"myname\""; assertEquals(httpGet("myRoot", path), val); + assertEquals(httpGet("myUser", path), val); assertEquals(httpGet("myReadonly", path), val); assert404("myMinimal", path); // can't see app, to retrieve entity assert404("unrecognisedUser", path); @@ -68,6 +69,7 @@ public void testCurrentState() throws Exception { String regex = ".*"+confName+".*myname.*"; Asserts.assertStringMatchesRegex(httpGet("myRoot", path), regex); + Asserts.assertStringMatchesRegex(httpGet("myUser", path), regex); Asserts.assertStringMatchesRegex(httpGet("myReadonly", path), regex); assert404("myMinimal", path); // can't see app, to retrieve entity assert404("unrecognisedUser", path); 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 index dab72ecce3..931b7ae327 100644 --- 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 @@ -53,6 +53,7 @@ public void testGet() throws Exception { String val = "\"myval\""; assertEquals(httpGet("myRoot", path), val); + assertEquals(httpGet("myUser", path), val); assertEquals(httpGet("myReadonly", path), val); assert404("myMinimal", path); // can't see app, to retrieve entity assert404("unrecognisedUser", path); @@ -73,6 +74,7 @@ public void testCurrentState() throws Exception { String regex = ".*"+sensorName+".*myval.*"; Asserts.assertStringMatchesRegex(httpGet("myRoot", path), regex); + Asserts.assertStringMatchesRegex(httpGet("myUser", path), regex); Asserts.assertStringMatchesRegex(httpGet("myReadonly", path), regex); assert404("myMinimal", path); // can't see app, to retrieve entity assert404("unrecognisedUser", path); From 4ff1e1ae999147ae087e2adcec821ad0dbe3fc88 Mon Sep 17 00:00:00 2001 From: Sam Corbett Date: Thu, 7 Apr 2016 15:54:47 +0100 Subject: [PATCH 3/3] Add actions to SEE_ALL_SERVER_INFO entitlement group Groovy console and reloading Brookyln properties. --- .../rest/resources/ScriptResource.java | 6 ++ .../rest/resources/ServerResource.java | 6 +- .../ScriptApiEntitlementsTest.java | 56 +++++++++++++++++++ .../ServerApiEntitlementsTest.java | 24 ++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/ScriptApiEntitlementsTest.java diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java index 77989c36db..7b558b6765 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java @@ -18,8 +18,10 @@ */ package org.apache.brooklyn.rest.resources; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; import org.apache.brooklyn.rest.api.ScriptApi; import org.apache.brooklyn.rest.domain.ScriptExecutionSummary; +import org.apache.brooklyn.rest.util.WebResourceUtils; import org.apache.brooklyn.util.stream.ThreadLocalPrintStream; import org.apache.brooklyn.util.stream.ThreadLocalPrintStream.OutputCapturingContext; @@ -45,6 +47,10 @@ public class ScriptResource extends AbstractBrooklynRestResource implements Scri @SuppressWarnings("rawtypes") @Override public ScriptExecutionSummary groovy(HttpServletRequest request, String script) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user()); + } + log.info("Web REST executing user-supplied script"); if (log.isDebugEnabled()) { log.debug("Web REST user-supplied script contents:\n"+script); diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java index 0b99fc863f..7be07de041 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java @@ -96,7 +96,11 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv @Override public void reloadBrooklynProperties() { - brooklyn().reloadBrooklynProperties(); + if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null)) { + brooklyn().reloadBrooklynProperties(); + } else { + throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user()); + } } private boolean isMaster() { diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/ScriptApiEntitlementsTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/ScriptApiEntitlementsTest.java new file mode 100644 index 0000000000..5f6498a007 --- /dev/null +++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/ScriptApiEntitlementsTest.java @@ -0,0 +1,56 @@ +/* + * 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 java.net.URI; +import java.util.Map; + +import org.apache.brooklyn.util.http.HttpTool; +import org.apache.brooklyn.util.http.HttpToolResponse; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; + +public class ScriptApiEntitlementsTest extends AbstractRestApiEntitlementsTest { + + @Test(groups = "Integration") + public void testGroovy() throws Exception { + String script = "1 + 1"; + HttpToolResponse rootRepsonse = httpPost("myRoot", "/v1/script/groovy", script.getBytes()); + assertHealthyStatusCode(rootRepsonse); + Map groovyOutput = new Gson().fromJson(rootRepsonse.getContentAsString(), Map.class); + assertEquals(groovyOutput.get("result"), "2"); + assertForbiddenPost("myUser", "/v1/script/groovy", script.getBytes()); + assertForbiddenPost("myReadonly", "/v1/script/groovy", script.getBytes()); + assertForbiddenPost("myMinimal", "/v1/script/groovy", script.getBytes()); + assertForbiddenPost("unrecognisedUser", "/v1/script/groovy", script.getBytes()); + } + + @Override + protected HttpToolResponse httpPost(String user, String path, byte[] body) throws Exception { + final ImmutableMap headers = ImmutableMap.of( + "Content-Type", "application/text"); + final URI uri = URI.create(getBaseUriRest()).resolve(path); + return HttpTool.httpPost(newClient(user), uri, headers, body); + } +} 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 index afa42cb4f5..ca539765aa 100644 --- 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 @@ -18,6 +18,7 @@ */ package org.apache.brooklyn.rest.entitlement; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; import org.testng.annotations.Test; @Test(singleThreaded = true) @@ -27,8 +28,31 @@ public class ServerApiEntitlementsTest extends AbstractRestApiEntitlementsTest { public void testGetHealthy() throws Exception { String path = "/v1/server/up"; assertPermitted("myRoot", path); + assertPermitted("myUser", path); assertForbidden("myReadonly", path); assertForbidden("myMinimal", path); assertForbidden("unrecognisedUser", path); } + + @Test(groups = "Integration") + public void testReloadProperties() throws Exception { + String resource = "/v1/server/properties/reload"; + assertPermittedPost("myRoot", resource, null); + assertForbiddenPost("myUser", resource, null); + assertForbiddenPost("myReadonly", resource, null); + assertForbiddenPost("myMinimal", resource, null); + assertForbiddenPost("unrecognisedUser", resource, null); + } + + @Test(groups = "Integration") + public void testGetConfig() throws Exception { + // Property set in test setup. + String path = "/v1/server/config/" + Entitlements.GLOBAL_ENTITLEMENT_MANAGER.getName(); + assertPermitted("myRoot", path); + assertForbidden("myUser", path); + assertForbidden("myReadonly", path); + assertForbidden("myMinimal", path); + assertForbidden("unrecognisedUser", path); + } + }