http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java
new file mode 100644
index 0000000..c8a7962
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.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.resources;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
+import org.apache.brooklyn.rest.api.PolicyConfigApi;
+import org.apache.brooklyn.rest.domain.PolicyConfigSummary;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.PolicyTransformer;
+import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+@HaHotStateRequired
+public class PolicyConfigResource extends AbstractBrooklynRestResource 
implements PolicyConfigApi {
+
+    @Override
+    public List<PolicyConfigSummary> list(
+            final String application, final String entityToken, final String 
policyToken) {
+        Entity entity = brooklyn().getEntity(application, entityToken);
+        Policy policy = brooklyn().getPolicy(entity, policyToken);
+
+        List<PolicyConfigSummary> result = Lists.newArrayList();
+        for (ConfigKey<?> key : policy.getPolicyType().getConfigKeys()) {
+            result.add(PolicyTransformer.policyConfigSummary(brooklyn(), 
entity, policy, key, ui.getBaseUriBuilder()));
+        }
+        return result;
+    }
+
+    // TODO support parameters  ?show=value,summary&name=xxx 
&format={string,json,xml}
+    // (and in sensors class)
+    @Override
+    public Map<String, Object> batchConfigRead(String application, String 
entityToken, String policyToken) {
+        // TODO: add test
+        Policy policy = brooklyn().getPolicy(application, entityToken, 
policyToken);
+        Map<String, Object> source = 
((BrooklynObjectInternal)policy).config().getBag().getAllConfig();
+        Map<String, Object> result = Maps.newLinkedHashMap();
+        for (Map.Entry<String, Object> ek : source.entrySet()) {
+            result.put(ek.getKey(), getStringValueForDisplay(brooklyn(), 
policy, ek.getValue()));
+        }
+        return result;
+    }
+
+    @Override
+    public String get(String application, String entityToken, String 
policyToken, String configKeyName) {
+        Policy policy = brooklyn().getPolicy(application, entityToken, 
policyToken);
+        ConfigKey<?> ck = policy.getPolicyType().getConfigKey(configKeyName);
+        if (ck == null) throw WebResourceUtils.notFound("Cannot find config 
key '%s' in policy '%s' of entity '%s'", configKeyName, policy, entityToken);
+
+        return getStringValueForDisplay(brooklyn(), policy, 
policy.getConfig(ck));
+    }
+
+    @Override
+    @Deprecated
+    public Response set(String application, String entityToken, String 
policyToken, String configKeyName, String value) {
+        return set(application, entityToken, policyToken, configKeyName, 
(Object) value);
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public Response set(String application, String entityToken, String 
policyToken, String configKeyName, Object value) {
+        Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), 
Entitlements.MODIFY_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to 
modify entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+
+        Policy policy = brooklyn().getPolicy(application, entityToken, 
policyToken);
+        ConfigKey<?> ck = policy.getPolicyType().getConfigKey(configKeyName);
+        if (ck == null) throw WebResourceUtils.notFound("Cannot find config 
key '%s' in policy '%s' of entity '%s'", configKeyName, policy, entityToken);
+
+        policy.config().set((ConfigKey) ck, TypeCoercions.coerce(value, 
ck.getTypeToken()));
+
+        return Response.status(Response.Status.OK).build();
+    }
+
+    public static String getStringValueForDisplay(BrooklynRestResourceUtils 
utils, Policy policy, Object value) {
+        return utils.getStringValueForDisplay(value);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java
new file mode 100644
index 0000000..2696440
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java
@@ -0,0 +1,135 @@
+/*
+ * 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.resources;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.core.policy.Policies;
+import org.apache.brooklyn.rest.api.PolicyApi;
+import org.apache.brooklyn.rest.domain.PolicySummary;
+import org.apache.brooklyn.rest.domain.Status;
+import org.apache.brooklyn.rest.domain.SummaryComparators;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.ApplicationTransformer;
+import org.apache.brooklyn.rest.transform.PolicyTransformer;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Maps;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+@HaHotStateRequired
+public class PolicyResource extends AbstractBrooklynRestResource implements 
PolicyApi {
+
+    private static final Logger log = 
LoggerFactory.getLogger(PolicyResource.class);
+
+    private @Context UriInfo ui;
+
+    @Override
+    public List<PolicySummary> list( final String application, final String 
entityToken ) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        return FluentIterable.from(entity.policies())
+            .transform(new Function<Policy, PolicySummary>() {
+                @Override
+                public PolicySummary apply(Policy policy) {
+                    return PolicyTransformer.policySummary(entity, policy, 
ui.getBaseUriBuilder());
+                }
+            })
+            .toSortedList(SummaryComparators.nameComparator());
+    }
+
+    // TODO support parameters  ?show=value,summary&name=xxx
+    // (and in sensors class)
+    @Override
+    public Map<String, Boolean> batchConfigRead( String application, String 
entityToken) {
+        // TODO: add test
+        Entity entity = brooklyn().getEntity(application, entityToken);
+        Map<String, Boolean> result = Maps.newLinkedHashMap();
+        for (Policy p : entity.policies()) {
+            result.put(p.getId(), !p.isSuspended());
+        }
+        return result;
+    }
+
+    // TODO would like to make 'config' arg optional but jersey complains if 
we do
+    @SuppressWarnings("unchecked")
+    @Override
+    public PolicySummary addPolicy( String application,String entityToken, 
String policyTypeName,
+            Map<String, String> config) {
+        Entity entity = brooklyn().getEntity(application, entityToken);
+        Class<? extends Policy> policyType;
+        try {
+            policyType = (Class<? extends Policy>) 
Class.forName(policyTypeName);
+        } catch (ClassNotFoundException e) {
+            throw WebResourceUtils.badRequest("No policy with type %s found", 
policyTypeName);
+        } catch (ClassCastException e) {
+            throw WebResourceUtils.badRequest("No policy with type %s found", 
policyTypeName);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+
+        Policy policy = 
entity.policies().add(PolicySpec.create(policyType).configure(config));
+        log.debug("REST API added policy " + policy + " to " + entity);
+
+        return PolicyTransformer.policySummary(entity, policy, 
ui.getBaseUriBuilder());
+    }
+
+    @Override
+    public Status getStatus(String application, String entityToken, String 
policyId) {
+        Policy policy = brooklyn().getPolicy(application, entityToken, 
policyId);
+        return 
ApplicationTransformer.statusFromLifecycle(Policies.getPolicyStatus(policy));
+    }
+
+    @Override
+    public Response start( String application, String entityToken, String 
policyId) {
+        Policy policy = brooklyn().getPolicy(application, entityToken, 
policyId);
+
+        policy.resume();
+        return Response.status(Response.Status.NO_CONTENT).build();
+    }
+
+    @Override
+    public Response stop(String application, String entityToken, String 
policyId) {
+        Policy policy = brooklyn().getPolicy(application, entityToken, 
policyId);
+
+        policy.suspend();
+        return Response.status(Response.Status.NO_CONTENT).build();
+    }
+
+    @Override
+    public Response destroy(String application, String entityToken, String 
policyToken) {
+        Entity entity = brooklyn().getEntity(application, entityToken);
+        Policy policy = brooklyn().getPolicy(entity, policyToken);
+
+        policy.suspend();
+        entity.policies().remove(policy);
+        return Response.status(Response.Status.NO_CONTENT).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.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
new file mode 100644
index 0000000..77989c3
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java
@@ -0,0 +1,102 @@
+/*
+ * 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.resources;
+
+import org.apache.brooklyn.rest.api.ScriptApi;
+import org.apache.brooklyn.rest.domain.ScriptExecutionSummary;
+import org.apache.brooklyn.util.stream.ThreadLocalPrintStream;
+import 
org.apache.brooklyn.util.stream.ThreadLocalPrintStream.OutputCapturingContext;
+
+import groovy.lang.Binding;
+import groovy.lang.GroovyShell;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class ScriptResource extends AbstractBrooklynRestResource implements 
ScriptApi {
+
+    private static final Logger log = 
LoggerFactory.getLogger(ScriptResource.class);
+    
+    public static final String USER_DATA_MAP_SESSION_ATTRIBUTE = 
"brooklyn.script.groovy.user.data";
+    public static final String USER_LAST_VALUE_SESSION_ATTRIBUTE = 
"brooklyn.script.groovy.user.last";
+    
+    @SuppressWarnings("rawtypes")
+    @Override
+    public ScriptExecutionSummary groovy(HttpServletRequest request, String 
script) {
+        log.info("Web REST executing user-supplied script");
+        if (log.isDebugEnabled()) {
+            log.debug("Web REST user-supplied script contents:\n"+script);
+        }
+        
+        Binding binding = new Binding();
+        binding.setVariable("mgmt", mgmt());
+        
+        HttpSession session = request!=null ? request.getSession() : null;
+        if (session!=null) {
+            Map data = (Map) 
session.getAttribute(USER_DATA_MAP_SESSION_ATTRIBUTE);
+            if (data==null) {
+                data = new LinkedHashMap();
+                session.setAttribute(USER_DATA_MAP_SESSION_ATTRIBUTE, data);
+            }
+            binding.setVariable("data", data);
+
+            Object last = 
session.getAttribute(USER_LAST_VALUE_SESSION_ATTRIBUTE);
+            binding.setVariable("last", last);
+        }
+        
+        GroovyShell shell = new GroovyShell(binding);
+
+        OutputCapturingContext stdout = 
ThreadLocalPrintStream.stdout().captureTee();
+        OutputCapturingContext stderr = 
ThreadLocalPrintStream.stderr().captureTee();
+
+        Object value = null;
+        Throwable problem = null;
+        try {
+            value = shell.evaluate(script);
+            if (session!=null)
+                session.setAttribute(USER_LAST_VALUE_SESSION_ATTRIBUTE, value);
+        } catch (Throwable t) {
+            log.warn("Problem in user-supplied script: "+t, t);
+            problem = t;
+        } finally {
+            stdout.end();
+            stderr.end();
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Web REST user-supplied script completed:\n"+
+                    (value!=null ? "RESULT: "+value.toString()+"\n" : "")+ 
+                    (problem!=null ? "ERROR: "+problem.toString()+"\n" : "")+
+                    (!stdout.isEmpty() ? "STDOUT: "+stdout.toString()+"\n" : 
"")+
+                    (!stderr.isEmpty() ? "STDERR: "+stderr.toString()+"\n" : 
""));
+        }
+
+        // call toString on the result, in case it is not serializable
+        return new ScriptExecutionSummary(
+                value!=null ? value.toString() : null, 
+                problem!=null ? problem.toString() : null,
+                stdout.toString(), stderr.toString());
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
new file mode 100644
index 0000000..750b7de
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
@@ -0,0 +1,184 @@
+/*
+ * 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.resources;
+
+import static com.google.common.collect.Iterables.filter;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+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;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.SensorTransformer;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.core.task.ValueResolver;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+@HaHotStateRequired
+public class SensorResource extends AbstractBrooklynRestResource implements 
SensorApi {
+
+    private static final Logger log = 
LoggerFactory.getLogger(SensorResource.class);
+
+    @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.forbidden("User '%s' is not authorized to 
see entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+
+        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, 
ui.getBaseUriBuilder()));
+        }
+        
+        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.forbidden("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());
+        }
+        return sensorMap;
+    }
+
+    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.forbidden("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.forbidden("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();
+    }
+
+    @Override
+    public String getPlain(String application, String entityToken, String 
sensorName, final Boolean raw) {
+        return (String) get(false, application, entityToken, sensorName, raw);
+    }
+
+    @Override
+    public Object get(final String application, final String entityToken, 
String sensorName, final Boolean raw) {
+        return get(true, application, entityToken, sensorName, raw);
+    }
+
+    private AttributeSensor<?> findSensor(Entity entity, String name) {
+        Sensor<?> s = entity.getEntityType().getSensor(name);
+        if (s instanceof AttributeSensor) return (AttributeSensor<?>) s;
+        return new BasicAttributeSensor<Object>(Object.class, name);
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    public void setFromMap(String application, String entityToken, Map 
newValues) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), 
Entitlements.MODIFY_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to 
modify entity '%s'",
+                Entitlements.getEntitlementContext().user(), entity);
+        }
+
+        if (log.isDebugEnabled())
+            log.debug("REST user "+Entitlements.getEntitlementContext()+" 
setting sensors "+newValues);
+        for (Object entry: newValues.entrySet()) {
+            String sensorName = Strings.toString(((Map.Entry)entry).getKey());
+            Object newValue = ((Map.Entry)entry).getValue();
+            
+            AttributeSensor sensor = findSensor(entity, sensorName);
+            entity.sensors().set(sensor, newValue);
+        }
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    public void set(String application, String entityToken, String sensorName, 
Object newValue) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), 
Entitlements.MODIFY_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("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()+" 
setting sensor "+sensorName+" to "+newValue);
+        entity.sensors().set(sensor, newValue);
+    }
+    
+    @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.forbidden("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);
+        ((EntityInternal)entity).sensors().remove(sensor);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..fe7893b
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java
@@ -0,0 +1,497 @@
+/*
+ * 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.resources;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ContextResolver;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext;
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityManager;
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
+import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
+import org.apache.brooklyn.api.mgmt.ha.ManagementPlaneSyncRecord;
+import org.apache.brooklyn.api.mgmt.ha.MementoCopyMode;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.BrooklynVersion;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.StartableApplication;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.mgmt.persist.BrooklynPersistenceUtils;
+import org.apache.brooklyn.core.mgmt.persist.FileBasedObjectStore;
+import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore;
+import org.apache.brooklyn.rest.api.ServerApi;
+import org.apache.brooklyn.rest.domain.BrooklynFeatureSummary;
+import org.apache.brooklyn.rest.domain.HighAvailabilitySummary;
+import org.apache.brooklyn.rest.domain.VersionSummary;
+import org.apache.brooklyn.rest.transform.BrooklynFeatureTransformer;
+import org.apache.brooklyn.rest.transform.HighAvailabilityTransformer;
+import org.apache.brooklyn.rest.util.ShutdownHandler;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.file.ArchiveBuilder;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.CountdownTimer;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.FluentIterable;
+
+public class ServerResource extends AbstractBrooklynRestResource implements 
ServerApi {
+
+    private static final int SHUTDOWN_TIMEOUT_CHECK_INTERVAL = 200;
+
+    private static final Logger log = 
LoggerFactory.getLogger(ServerResource.class);
+
+    private static final String BUILD_SHA_1_PROPERTY = "git-sha-1";
+    private static final String BUILD_BRANCH_PROPERTY = "git-branch-name";
+    
+    @Context
+    private ContextResolver<ShutdownHandler> shutdownHandler;
+
+    @Override
+    public void reloadBrooklynProperties() {
+        brooklyn().reloadBrooklynProperties();
+    }
+
+    private boolean isMaster() {
+        return 
ManagementNodeState.MASTER.equals(mgmt().getHighAvailabilityManager().getNodeState());
+    }
+
+    @Override
+    public void shutdown(final boolean stopAppsFirst, final boolean 
forceShutdownOnError,
+            String shutdownTimeoutRaw, String requestTimeoutRaw, String 
delayForHttpReturnRaw,
+            Long delayMillis) {
+        
+        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("REST call to shutdown server, 
stopAppsFirst="+stopAppsFirst+", delayForHttpReturn="+shutdownTimeoutRaw);
+
+        if (stopAppsFirst && !isMaster()) {
+            log.warn("REST call to shutdown non-master server while stopping 
apps is disallowed");
+            throw WebResourceUtils.forbidden("Not allowed to stop all apps 
when server is not master");
+        }
+        final Duration shutdownTimeout = parseDuration(shutdownTimeoutRaw, 
Duration.of(20, TimeUnit.SECONDS));
+        Duration requestTimeout = parseDuration(requestTimeoutRaw, 
Duration.of(20, TimeUnit.SECONDS));
+        final Duration delayForHttpReturn;
+        if (delayMillis == null) {
+            delayForHttpReturn = parseDuration(delayForHttpReturnRaw, 
Duration.FIVE_SECONDS);
+        } else {
+            log.warn("'delayMillis' is deprecated, use 'delayForHttpReturn' 
instead.");
+            delayForHttpReturn = Duration.of(delayMillis, 
TimeUnit.MILLISECONDS);
+        }
+
+        Preconditions.checkState(delayForHttpReturn.nanos() >= 0, "Only 
positive or 0 delay allowed for delayForHttpReturn");
+
+        boolean isSingleTimeout = shutdownTimeout.equals(requestTimeout);
+        final AtomicBoolean completed = new AtomicBoolean();
+        final AtomicBoolean hasAppErrorsOrTimeout = new AtomicBoolean();
+
+        //shutdownHandler is thread local
+        final ShutdownHandler handler = 
shutdownHandler.getContext(ShutdownHandler.class);
+        new Thread("shutdown") {
+            @Override
+            public void run() {
+                boolean terminateTried = false;
+                ManagementContext mgmt = mgmt();
+                try {
+                    if (stopAppsFirst) {
+                        CountdownTimer shutdownTimeoutTimer = null;
+                        if (!shutdownTimeout.equals(Duration.ZERO)) {
+                            shutdownTimeoutTimer = 
shutdownTimeout.countdownTimer();
+                        }
+
+                        log.debug("Stopping applications");
+                        List<Task<?>> stoppers = new ArrayList<Task<?>>();
+                        int allStoppableApps = 0;
+                        for (Application app: mgmt.getApplications()) {
+                            allStoppableApps++;
+                            Lifecycle appState = 
app.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
+                            if (app instanceof StartableApplication &&
+                                    // Don't try to stop an already stopping 
app. Subsequent stops will complete faster
+                                    // cancelling the first stop task.
+                                    appState != Lifecycle.STOPPING) {
+                                stoppers.add(Entities.invokeEffector(app, app, 
StartableApplication.STOP));
+                            } else {
+                                log.debug("App " + app + " is already 
stopping, will not stop second time. Will wait for original stop to complete.");
+                            }
+                        }
+
+                        log.debug("Waiting for " + allStoppableApps + " apps 
to stop, of which " + stoppers.size() + " stopped explicitly.");
+                        for (Task<?> t: stoppers) {
+                            if (!waitAppShutdown(shutdownTimeoutTimer, t)) {
+                                //app stop error
+                                hasAppErrorsOrTimeout.set(true);
+                            }
+                        }
+
+                        // Wait for apps which were already stopping when we 
tried to shut down.
+                        if (hasStoppableApps(mgmt)) {
+                            log.debug("Apps are still stopping, wait for 
proper unmanage.");
+                            while (hasStoppableApps(mgmt) && 
(shutdownTimeoutTimer == null || !shutdownTimeoutTimer.isExpired())) {
+                                Duration wait;
+                                if (shutdownTimeoutTimer != null) {
+                                    wait = 
Duration.min(shutdownTimeoutTimer.getDurationRemaining(), Duration.ONE_SECOND);
+                                } else {
+                                    wait = Duration.ONE_SECOND;
+                                }
+                                Time.sleep(wait);
+                            }
+                            if (hasStoppableApps(mgmt)) {
+                                hasAppErrorsOrTimeout.set(true);
+                            }
+                        }
+                    }
+
+                    terminateTried = true;
+                    ((ManagementContextInternal)mgmt).terminate(); 
+
+                } catch (Throwable e) {
+                    Throwable interesting = Exceptions.getFirstInteresting(e);
+                    if (interesting instanceof TimeoutException) {
+                        //timeout while waiting for apps to stop
+                        log.warn("Timeout shutting down: 
"+Exceptions.collapseText(e));
+                        log.debug("Timeout shutting down: "+e, e);
+                        hasAppErrorsOrTimeout.set(true);
+                        
+                    } else {
+                        // swallow fatal, so we notify the outer loop to 
continue with shutdown
+                        log.error("Unexpected error shutting down: 
"+Exceptions.collapseText(e), e);
+                        
+                    }
+                    hasAppErrorsOrTimeout.set(true);
+                    
+                    if (!terminateTried) {
+                        ((ManagementContextInternal)mgmt).terminate(); 
+                    }
+                } finally {
+
+                    complete();
+                
+                    if (!hasAppErrorsOrTimeout.get() || forceShutdownOnError) {
+                        //give the http request a chance to complete 
gracefully, the server will be stopped in a shutdown hook
+                        Time.sleep(delayForHttpReturn);
+
+                        if (handler != null) {
+                            handler.onShutdownRequest();
+                        } else {
+                            log.warn("ShutdownHandler not set, exiting 
process");
+                            System.exit(0);
+                        }
+                        
+                    } else {
+                        // There are app errors, don't exit the process, 
allowing any exception to continue throwing
+                        log.warn("Abandoning shutdown because there were 
errors and shutdown was not forced.");
+                        
+                    }
+                }
+            }
+
+            private boolean hasStoppableApps(ManagementContext mgmt) {
+                for (Application app : mgmt.getApplications()) {
+                    if (app instanceof StartableApplication) {
+                        Lifecycle state = 
app.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
+                        if (state != Lifecycle.STOPPING && state != 
Lifecycle.STOPPED) {
+                            log.warn("Shutting down, expecting all apps to be 
in stopping state, but found application " + app + " to be in state " + state + 
". Just started?");
+                        }
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            private void complete() {
+                synchronized (completed) {
+                    completed.set(true);
+                    completed.notifyAll();
+                }
+            }
+
+            private boolean waitAppShutdown(CountdownTimer 
shutdownTimeoutTimer, Task<?> t) throws TimeoutException {
+                Duration waitInterval = null;
+                //wait indefinitely if no shutdownTimeoutTimer 
(shutdownTimeout == 0)
+                if (shutdownTimeoutTimer != null) {
+                    waitInterval = 
Duration.of(SHUTDOWN_TIMEOUT_CHECK_INTERVAL, TimeUnit.MILLISECONDS);
+                }
+                // waitInterval == null - blocks indefinitely
+                while(!t.blockUntilEnded(waitInterval)) {
+                    if (shutdownTimeoutTimer.isExpired()) {
+                        log.warn("Timeout while waiting for applications to 
stop at "+t+".\n"+t.getStatusDetail(true));
+                        throw new TimeoutException();
+                    }
+                }
+                if (t.isError()) {
+                    log.warn("Error stopping application "+t+" during shutdown 
(ignoring)\n"+t.getStatusDetail(true));
+                    return false;
+                } else {
+                    return true;
+                }
+            }
+        }.start();
+
+        synchronized (completed) {
+            if (!completed.get()) {
+                try {
+                    long waitTimeout = 0;
+                    //If the timeout for both shutdownTimeout and 
requestTimeout is equal
+                    //then better wait until the 'completed' flag is set, 
rather than timing out
+                    //at just about the same time (i.e. always wait for the 
shutdownTimeout in this case).
+                    //This will prevent undefined behaviour where either one 
of shutdownTimeout or requestTimeout
+                    //will be first to expire and the error flag won't be set 
predictably, it will
+                    //toggle depending on which expires first.
+                    //Note: shutdownTimeout is checked at 
SHUTDOWN_TIMEOUT_CHECK_INTERVAL interval, meaning it is
+                    //practically rounded up to the nearest 
SHUTDOWN_TIMEOUT_CHECK_INTERVAL.
+                    if (!isSingleTimeout) {
+                        waitTimeout = requestTimeout.toMilliseconds();
+                    }
+                    completed.wait(waitTimeout);
+                } catch (InterruptedException e) {
+                    throw Exceptions.propagate(e);
+                }
+            }
+        }
+
+        if (hasAppErrorsOrTimeout.get()) {
+            WebResourceUtils.badRequest("Error or timeout while stopping 
applications. See log for details.");
+        }
+    }
+
+    private Duration parseDuration(String str, Duration defaultValue) {
+        if (Strings.isEmpty(str)) {
+            return defaultValue;
+        } else {
+            return Duration.parse(str);
+        }
+    }
+
+    @Override
+    public VersionSummary getVersion() {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), 
Entitlements.SERVER_STATUS, null))
+            throw WebResourceUtils.forbidden("User '%s' is not authorized for 
this operation", Entitlements.getEntitlementContext().user());
+        
+        // TODO
+        // * "build-metadata.properties" is probably the wrong name
+        // * we should include brooklyn.version and a build timestamp in this 
file
+        // * the authority for brooklyn should probably be core rather than 
brooklyn-rest-server
+        InputStream input = 
ResourceUtils.create().getResourceFromUrl("classpath://build-metadata.properties");
+        Properties properties = new Properties();
+        String gitSha1 = null, gitBranch = null;
+        try {
+            properties.load(input);
+            gitSha1 = properties.getProperty(BUILD_SHA_1_PROPERTY);
+            gitBranch = properties.getProperty(BUILD_BRANCH_PROPERTY);
+        } catch (IOException e) {
+            log.error("Failed to load build-metadata.properties", e);
+        }
+        gitSha1 = BrooklynVersion.INSTANCE.getSha1FromOsgiManifest();
+
+        FluentIterable<BrooklynFeatureSummary> features = 
FluentIterable.from(BrooklynVersion.getFeatures(mgmt()))
+                .transform(BrooklynFeatureTransformer.FROM_FEATURE);
+
+        return new VersionSummary(BrooklynVersion.get(), gitSha1, gitBranch, 
features.toList());
+    }
+
+    @Override
+    public boolean isUp() {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), 
Entitlements.SERVER_STATUS, null))
+            throw WebResourceUtils.forbidden("User '%s' is not authorized for 
this operation", Entitlements.getEntitlementContext().user());
+
+        Maybe<ManagementContext> mm = mgmtMaybe();
+        return !mm.isAbsent() && mm.get().isStartupComplete() && 
mm.get().isRunning();
+    }
+    
+    @Override
+    public boolean isShuttingDown() {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), 
Entitlements.SERVER_STATUS, null))
+            throw WebResourceUtils.forbidden("User '%s' is not authorized for 
this operation", Entitlements.getEntitlementContext().user());
+        Maybe<ManagementContext> mm = mgmtMaybe();
+        return !mm.isAbsent() && mm.get().isStartupComplete() && 
!mm.get().isRunning();
+    }
+    
+    @Override
+    public boolean isHealthy() {
+        return isUp() && ((ManagementContextInternal) 
mgmt()).errors().isEmpty();
+    }
+    
+    @Override
+    public Map<String,Object> getUpExtended() {
+        return MutableMap.<String,Object>of(
+            "up", isUp(),
+            "shuttingDown", isShuttingDown(),
+            "healthy", isHealthy(),
+            "ha", getHighAvailabilityPlaneStates());
+    }
+    
+    
+    @Deprecated
+    @Override
+    public String getStatus() {
+        return getHighAvailabilityNodeState().toString();
+    }
+
+    @Override
+    public String getConfig(String configKey) {
+        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());
+        }
+        ConfigKey<String> config = ConfigKeys.newStringConfigKey(configKey);
+        return mgmt().getConfig().getConfig(config);
+    }
+
+    @Deprecated
+    @Override
+    public HighAvailabilitySummary getHighAvailability() {
+        return getHighAvailabilityPlaneStates();
+    }
+
+    @Override
+    public ManagementNodeState getHighAvailabilityNodeState() {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), 
Entitlements.SERVER_STATUS, null))
+            throw WebResourceUtils.forbidden("User '%s' is not authorized for 
this operation", Entitlements.getEntitlementContext().user());
+        
+        Maybe<ManagementContext> mm = mgmtMaybe();
+        if (mm.isAbsent()) return ManagementNodeState.INITIALIZING;
+        return mm.get().getHighAvailabilityManager().getNodeState();
+    }
+
+    @Override
+    public ManagementNodeState 
setHighAvailabilityNodeState(HighAvailabilityMode mode) {
+        if (mode==null)
+            throw new IllegalStateException("Missing parameter: mode");
+        
+        HighAvailabilityManager haMgr = mgmt().getHighAvailabilityManager();
+        ManagementNodeState existingState = haMgr.getNodeState();
+        haMgr.changeMode(mode);
+        return existingState;
+    }
+
+    @Override
+    public Map<String, Object> getHighAvailabilityMetrics() {
+        return mgmt().getHighAvailabilityManager().getMetrics();
+    }
+    
+    @Override
+    public long getHighAvailabitlityPriority() {
+        return mgmt().getHighAvailabilityManager().getPriority();
+    }
+
+    @Override
+    public long setHighAvailabilityPriority(long priority) {
+        HighAvailabilityManager haMgr = mgmt().getHighAvailabilityManager();
+        long oldPrio = haMgr.getPriority();
+        haMgr.setPriority(priority);
+        return oldPrio;
+    }
+
+    @Override
+    public HighAvailabilitySummary getHighAvailabilityPlaneStates() {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), 
Entitlements.SERVER_STATUS, null))
+            throw WebResourceUtils.forbidden("User '%s' is not authorized for 
this operation", Entitlements.getEntitlementContext().user());
+        ManagementPlaneSyncRecord memento = 
mgmt().getHighAvailabilityManager().getLastManagementPlaneSyncRecord();
+        if (memento==null) memento = 
mgmt().getHighAvailabilityManager().loadManagementPlaneSyncRecord(true);
+        if (memento==null) return null;
+        return 
HighAvailabilityTransformer.highAvailabilitySummary(mgmt().getManagementNodeId(),
 memento);
+    }
+
+    @Override
+    public Response clearHighAvailabilityPlaneStates() {
+        mgmt().getHighAvailabilityManager().publishClearNonMaster();
+        return Response.ok().build();
+    }
+
+    @Override
+    public String getUser() {
+        EntitlementContext entitlementContext = 
Entitlements.getEntitlementContext();
+        if (entitlementContext!=null && entitlementContext.user()!=null){
+            return entitlementContext.user();
+        } else {
+            return null; //User can be null if no authentication was requested
+        }
+    }
+
+    @Override
+    public Response exportPersistenceData(String preferredOrigin) {
+        return exportPersistenceData(TypeCoercions.coerce(preferredOrigin, 
MementoCopyMode.class));
+    }
+    
+    protected Response exportPersistenceData(MementoCopyMode preferredOrigin) {
+        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());
+
+        File dir = null;
+        try {
+            String label = 
mgmt().getManagementNodeId()+"-"+Time.makeDateSimpleStampString();
+            PersistenceObjectStore targetStore = 
BrooklynPersistenceUtils.newPersistenceObjectStore(mgmt(), null, 
+                "tmp/web-persistence-"+label+"-"+Identifiers.makeRandomId(4));
+            dir = ((FileBasedObjectStore)targetStore).getBaseDir();
+            // only register the parent dir because that will prevent leaks 
for the random ID
+            Os.deleteOnExitEmptyParentsUpTo(dir.getParentFile(), 
dir.getParentFile());
+            BrooklynPersistenceUtils.writeMemento(mgmt(), targetStore, 
preferredOrigin);            
+            
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ArchiveBuilder.zip().addDirContentsAt( 
((FileBasedObjectStore)targetStore).getBaseDir(), 
((FileBasedObjectStore)targetStore).getBaseDir().getName() ).stream(baos);
+            Os.deleteRecursively(dir);
+            String filename = "brooklyn-state-"+label+".zip";
+            return Response.ok(baos.toByteArray(), 
MediaType.APPLICATION_OCTET_STREAM_TYPE)
+                .header("Content-Disposition","attachment; filename = 
"+filename)
+                .build();
+        } catch (Exception e) {
+            log.warn("Unable to serve persistence data (rethrowing): "+e, e);
+            if (dir!=null) {
+                try {
+                    Os.deleteRecursively(dir);
+                } catch (Exception e2) {
+                    log.warn("Ignoring error deleting '"+dir+"' after another 
error, throwing original error ("+e+"); ignored error deleting is: "+e2);
+                }
+            }
+            throw Exceptions.propagate(e);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java
new file mode 100644
index 0000000..ebcd2fa
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java
@@ -0,0 +1,256 @@
+/*
+ * 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.resources;
+
+import static org.apache.brooklyn.rest.util.WebResourceUtils.notFound;
+
+import java.net.URI;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage;
+import org.apache.brooklyn.core.mgmt.usage.LocationUsage;
+import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage.ApplicationEvent;
+import org.apache.brooklyn.rest.api.UsageApi;
+import org.apache.brooklyn.rest.domain.UsageStatistic;
+import org.apache.brooklyn.rest.domain.UsageStatistics;
+import org.apache.brooklyn.rest.transform.ApplicationTransformer;
+import org.apache.brooklyn.util.exceptions.UserFacingException;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Time;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+
+public class UsageResource extends AbstractBrooklynRestResource implements 
UsageApi {
+
+    private static final Logger log = 
LoggerFactory.getLogger(UsageResource.class);
+
+    private static final Set<Lifecycle> WORKING_LIFECYCLES = 
ImmutableSet.of(Lifecycle.RUNNING, Lifecycle.CREATED, Lifecycle.STARTING);
+
+    @Override
+    public List<UsageStatistics> listApplicationsUsage(@Nullable String start, 
@Nullable String end) {
+        log.debug("REST call to get application usage for all applications: 
dates {} -> {}", new Object[] {start, end});
+        
+        List<UsageStatistics> response = Lists.newArrayList();
+        
+        Date startDate = parseDate(start, new Date(0));
+        Date endDate = parseDate(end, new Date());
+        
+        checkDates(startDate, endDate);
+
+        Set<ApplicationUsage> usages = ((ManagementContextInternal) 
mgmt()).getUsageManager().getApplicationUsage(Predicates.alwaysTrue());
+
+        for (ApplicationUsage usage : usages) {
+            List<UsageStatistic> statistics = retrieveApplicationUsage(usage, 
startDate, endDate);
+            if (statistics.size() > 0) {
+                response.add(new UsageStatistics(statistics, 
ImmutableMap.<String,URI>of()));
+            }
+        }
+        return response;
+    }
+    
+    @Override
+    public UsageStatistics getApplicationUsage(String application, String 
start, String end) {
+        log.debug("REST call to get application usage for application {}: 
dates {} -> {}", new Object[] {application, start, end});
+        
+        Date startDate = parseDate(start, new Date(0));
+        Date endDate = parseDate(end, new Date());
+
+        checkDates(startDate, endDate);
+
+        ApplicationUsage usage = ((ManagementContextInternal) 
mgmt()).getUsageManager().getApplicationUsage(application);
+        if (usage != null) {
+            List<UsageStatistic> statistics = retrieveApplicationUsage(usage, 
startDate, endDate);
+            return new UsageStatistics(statistics, 
ImmutableMap.<String,URI>of());
+        } else {
+            throw notFound("Application '%s' not found", application);
+        }
+    }
+
+    private List<UsageStatistic> retrieveApplicationUsage(ApplicationUsage 
usage, Date startDate, Date endDate) {
+        log.debug("Determining application usage for application {}: dates {} 
-> {}", new Object[] {usage.getApplicationId(), startDate, endDate});
+        log.trace("Considering application usage events of {}: {}", 
usage.getApplicationId(), usage.getEvents());
+
+        List<UsageStatistic> result = Lists.newArrayList();
+
+        // Getting duration of state by comparing with next event (if next 
event is of same type, we just generate two statistics)...
+        for (int i = 0; i < usage.getEvents().size(); i++) {
+            ApplicationEvent current = usage.getEvents().get(i);
+            Date eventStartDate = current.getDate();
+            Date eventEndDate;
+
+            if (i <  usage.getEvents().size() - 1) {
+                ApplicationEvent next =  usage.getEvents().get(i + 1);
+                eventEndDate = next.getDate();
+            } else if (current.getState() == Lifecycle.DESTROYED) {
+                eventEndDate = eventStartDate;
+            } else {
+                eventEndDate = new Date();
+            }
+
+            if (eventStartDate.compareTo(endDate) > 0 || 
eventEndDate.compareTo(startDate) < 0) {
+                continue;
+            }
+
+            if (eventStartDate.compareTo(startDate) < 0) {
+                eventStartDate = startDate;
+            }
+            if (eventEndDate.compareTo(endDate) > 0) {
+                eventEndDate = endDate;
+            }
+            long duration = eventEndDate.getTime() - eventStartDate.getTime();
+            UsageStatistic statistic = new 
UsageStatistic(ApplicationTransformer.statusFromLifecycle(current.getState()), 
usage.getApplicationId(), usage.getApplicationId(), format(eventStartDate), 
format(eventEndDate), duration, usage.getMetadata());
+            log.trace("Adding application usage statistic to response for app 
{}: {}", usage.getApplicationId(), statistic);
+            result.add(statistic);
+        }
+        
+        return result;
+    }
+
+    @Override
+    public List<UsageStatistics> listMachinesUsage(final String application, 
final String start, final String end) {
+        log.debug("REST call to get machine usage for application {}: dates {} 
-> {}", new Object[] {application, start, end});
+        
+        final Date startDate = parseDate(start, new Date(0));
+        final Date endDate = parseDate(end, new Date());
+
+        checkDates(startDate, endDate);
+        
+        // Note currently recording ALL metrics for a machine that contains an 
Event from given Application
+        Set<LocationUsage> matches = ((ManagementContextInternal) 
mgmt()).getUsageManager().getLocationUsage(new Predicate<LocationUsage>() {
+            @Override
+            public boolean apply(LocationUsage input) {
+                LocationUsage.LocationEvent first = input.getEvents().get(0);
+                if (endDate.compareTo(first.getDate()) < 0) {
+                    return false;
+                }
+                LocationUsage.LocationEvent last = 
input.getEvents().get(input.getEvents().size() - 1);
+                if (!WORKING_LIFECYCLES.contains(last.getState()) && 
startDate.compareTo(last.getDate()) > 0) {
+                    return false;
+                }
+                if (application != null) {
+                    for (LocationUsage.LocationEvent e : input.getEvents()) {
+                        if (Objects.equal(application, e.getApplicationId())) {
+                            return true;
+                        }
+                    }
+                    return false;
+                }
+                return true;
+            }
+        });
+        
+        List<UsageStatistics> response = Lists.newArrayList();
+        for (LocationUsage usage : matches) {
+            List<UsageStatistic> statistics = retrieveMachineUsage(usage, 
startDate, endDate);
+            if (statistics.size() > 0) {
+                response.add(new UsageStatistics(statistics, 
ImmutableMap.<String,URI>of()));
+            }
+        }
+        return response;
+    }
+
+    @Override
+    public UsageStatistics getMachineUsage(final String machine, final String 
start, final String end) {
+        log.debug("REST call to get machine usage for machine {}: dates {} -> 
{}", new Object[] {machine, start, end});
+        
+        final Date startDate = parseDate(start, new Date(0));
+        final Date endDate = parseDate(end, new Date());
+
+        checkDates(startDate, endDate);
+        
+        // Note currently recording ALL metrics for a machine that contains an 
Event from given Application
+        LocationUsage usage = ((ManagementContextInternal) 
mgmt()).getUsageManager().getLocationUsage(machine);
+        
+        if (usage == null) {
+            throw notFound("Machine '%s' not found", machine);
+        }
+        
+        List<UsageStatistic> statistics = retrieveMachineUsage(usage, 
startDate, endDate);
+        return new UsageStatistics(statistics, ImmutableMap.<String,URI>of());
+    }
+
+    private List<UsageStatistic> retrieveMachineUsage(LocationUsage usage, 
Date startDate, Date endDate) {
+        log.debug("Determining machine usage for location {}", 
usage.getLocationId());
+        log.trace("Considering machine usage events of {}: {}", 
usage.getLocationId(), usage.getEvents());
+
+        List<UsageStatistic> result = Lists.newArrayList();
+
+        // Getting duration of state by comparing with next event (if next 
event is of same type, we just generate two statistics)...
+        for (int i = 0; i < usage.getEvents().size(); i++) {
+            LocationUsage.LocationEvent current = usage.getEvents().get(i);
+            Date eventStartDate = current.getDate();
+            Date eventEndDate;
+
+            if (i <  usage.getEvents().size() - 1) {
+                LocationUsage.LocationEvent next =  usage.getEvents().get(i + 
1);
+                eventEndDate = next.getDate();
+            } else if (current.getState() == Lifecycle.DESTROYED || 
current.getState() == Lifecycle.STOPPED) {
+                eventEndDate = eventStartDate;
+            } else {
+                eventEndDate = new Date();
+            }
+
+            if (eventStartDate.compareTo(endDate) > 0 || 
eventEndDate.compareTo(startDate) < 0) {
+                continue;
+            }
+
+            if (eventStartDate.compareTo(startDate) < 0) {
+                eventStartDate = startDate;
+            }
+            if (eventEndDate.compareTo(endDate) > 0) {
+                eventEndDate = endDate;
+            }
+            long duration = eventEndDate.getTime() - eventStartDate.getTime();
+            UsageStatistic statistic = new 
UsageStatistic(ApplicationTransformer.statusFromLifecycle(current.getState()), 
usage.getLocationId(), current.getApplicationId(), format(eventStartDate), 
format(eventEndDate), duration, usage.getMetadata());
+            log.trace("Adding machine usage statistic to response for app {}: 
{}", usage.getLocationId(), statistic);
+            result.add(statistic);
+        }
+        
+        return result;
+    }
+    
+    private void checkDates(Date startDate, Date endDate) {
+        if (startDate.compareTo(endDate) > 0) {
+            throw new UserFacingException(new IllegalArgumentException("Start 
must be less than or equal to end: " + startDate + " > " + endDate + 
+                    " (" + startDate.getTime() + " > " + endDate.getTime() + 
")"));
+        }
+    }
+
+    private Date parseDate(String toParse, Date def) {
+        return Strings.isBlank(toParse) ? def : Time.parseDate(toParse);
+    }
+    
+    private String format(Date date) {
+        return Time.makeDateString(date, Time.DATE_FORMAT_ISO8601_NO_MILLIS, 
Time.TIME_ZONE_UTC);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java
new file mode 100644
index 0000000..7492af6
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java
@@ -0,0 +1,32 @@
+/*
+ * 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.resources;
+
+import org.apache.brooklyn.core.BrooklynVersion;
+import org.apache.brooklyn.rest.api.VersionApi;
+
+/** @deprecated since 0.7.0; use /v1/server/version */
+@Deprecated
+public class VersionResource extends AbstractBrooklynRestResource implements 
VersionApi {
+
+    @Override
+    public String getVersion() {
+        return BrooklynVersion.get();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java
new file mode 100644
index 0000000..928a6bd
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java
@@ -0,0 +1,32 @@
+/*
+ * 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.security;
+
+import com.google.common.base.Charsets;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+
+public class PasswordHasher {
+    public static String sha256(String salt, String password) {
+        if (salt == null) salt = "";
+        byte[] bytes = (salt + password).getBytes(Charsets.UTF_8);
+        HashCode hash = Hashing.sha256().hashBytes(bytes);
+        return hash.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java
new file mode 100644
index 0000000..91c2523
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.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.security.provider;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.brooklyn.util.text.Strings;
+
+/**
+ * Provides default implementations of {@link #isAuthenticated(HttpSession)} 
and
+ * {@link #logout(HttpSession)}.
+ */
+public abstract class AbstractSecurityProvider implements SecurityProvider {
+
+    @Override
+    public boolean isAuthenticated(HttpSession session) {
+        if (session == null) return false;
+        Object value = session.getAttribute(getAuthenticationKey());
+        return Strings.isNonBlank(Strings.toString(value));
+    }
+
+    @Override
+    public boolean logout(HttpSession session) {
+        if (session == null) return false;
+        session.removeAttribute(getAuthenticationKey());
+        return true;
+    }
+
+    /**
+     * Sets an authentication token for the user on the session. Always 
returns true.
+     */
+    protected boolean allow(HttpSession session, String user) {
+        session.setAttribute(getAuthenticationKey(), user);
+        return true;
+    }
+
+    protected String getAuthenticationKey() {
+        return getClass().getName() + ".AUTHENTICATED";
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
new file mode 100644
index 0000000..97b4fe1
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
@@ -0,0 +1,40 @@
+/*
+ * 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.security.provider;
+
+import javax.servlet.http.HttpSession;
+
+/** provider who allows everyone */
+public class AnyoneSecurityProvider implements SecurityProvider {
+
+    @Override
+    public boolean isAuthenticated(HttpSession session) {
+        return true;
+    }
+
+    @Override
+    public boolean authenticate(HttpSession session, String user, String 
password) {
+        return true;
+    }
+
+    @Override
+    public boolean logout(HttpSession session) { 
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
new file mode 100644
index 0000000..a976975
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
@@ -0,0 +1,40 @@
+/*
+ * 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.security.provider;
+
+import javax.servlet.http.HttpSession;
+
+/** provider who disallows everyone */
+public class BlackholeSecurityProvider implements SecurityProvider {
+
+    @Override
+    public boolean isAuthenticated(HttpSession session) {
+        return false;
+    }
+
+    @Override
+    public boolean authenticate(HttpSession session, String user, String 
password) {
+        return false;
+    }
+
+    @Override
+    public boolean logout(HttpSession session) { 
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
new file mode 100644
index 0000000..7b8e4a5
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
@@ -0,0 +1,73 @@
+/*
+ * 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.security.provider;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.rest.BrooklynWebConfig;
+import org.apache.brooklyn.util.javalang.JavaClassNames;
+import org.apache.brooklyn.util.net.Networking;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BrooklynUserWithRandomPasswordSecurityProvider extends 
AbstractSecurityProvider implements SecurityProvider {
+
+    public static final Logger LOG = 
LoggerFactory.getLogger(BrooklynUserWithRandomPasswordSecurityProvider.class);
+    private static final String USER = "brooklyn";
+    private final String password;
+
+    public BrooklynUserWithRandomPasswordSecurityProvider() {
+        this.password = Identifiers.makeRandomId(10);
+        LOG.info("Allowing access to web console from localhost or with 
{}:{}", USER, password);
+    }
+
+    public BrooklynUserWithRandomPasswordSecurityProvider(ManagementContext 
mgmt) {
+        this();
+    }
+
+    @Override
+    public boolean authenticate(HttpSession session, String user, String 
password) {
+        if ((USER.equals(user) && this.password.equals(password)) || 
isRemoteAddressLocalhost(session)) {
+            return allow(session, user);
+        } else {
+            return false;
+        }
+    }
+
+    private boolean isRemoteAddressLocalhost(HttpSession session) {
+        Object remoteAddress = 
session.getAttribute(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE);
+        if (!(remoteAddress instanceof String)) return false;
+        if (Networking.isLocalhost((String)remoteAddress)) {
+            if (LOG.isTraceEnabled()) {
+                LOG.trace(this+": granting passwordless access to "+session+" 
originating from "+remoteAddress);
+            }
+            return true;
+        } else {
+            LOG.debug(this+": password required for "+session+" originating 
from "+remoteAddress);
+            return false;
+        }
+    }
+    
+    @Override
+    public String toString() {
+        return JavaClassNames.cleanSimpleClassName(this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
new file mode 100644
index 0000000..8b2b9da
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
@@ -0,0 +1,165 @@
+/*
+ * 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.security.provider;
+
+import java.lang.reflect.Constructor;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.servlet.http.HttpSession;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.config.StringConfigMap;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.rest.BrooklynWebConfig;
+import org.apache.brooklyn.util.text.Strings;
+
+public class DelegatingSecurityProvider implements SecurityProvider {
+
+    private static final Logger log = 
LoggerFactory.getLogger(DelegatingSecurityProvider.class);
+    protected final ManagementContext mgmt;
+
+    public DelegatingSecurityProvider(ManagementContext mgmt) {
+        this.mgmt = mgmt;
+        mgmt.addPropertiesReloadListener(new PropertiesListener());
+    }
+    
+    private SecurityProvider delegate;
+    private final AtomicLong modCount = new AtomicLong();
+
+    private class PropertiesListener implements 
ManagementContext.PropertiesReloadListener {
+        private static final long serialVersionUID = 8148722609022378917L;
+
+        @Override
+        public void reloaded() {
+            log.debug("{} reloading security provider", 
DelegatingSecurityProvider.this);
+            synchronized (DelegatingSecurityProvider.this) {
+                loadDelegate();
+                invalidateExistingSessions();
+            }
+        }
+    }
+
+    public synchronized SecurityProvider getDelegate() {
+        if (delegate == null) {
+            delegate = loadDelegate();
+        }
+        return delegate;
+    }
+
+    @SuppressWarnings("unchecked")
+    private synchronized SecurityProvider loadDelegate() {
+        StringConfigMap brooklynProperties = mgmt.getConfig();
+
+        SecurityProvider presetDelegate = 
brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE);
+        if (presetDelegate!=null) {
+            log.info("REST using pre-set security provider " + presetDelegate);
+            return presetDelegate;
+        }
+        
+        String className = 
brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME);
+
+        if (delegate != null && 
BrooklynWebConfig.hasNoSecurityOptions(mgmt.getConfig())) {
+            log.debug("{} refusing to change from {}: No security provider set 
in reloaded properties.",
+                    this, delegate);
+            return delegate;
+        }
+        log.info("REST using security provider " + className);
+
+        try {
+            Class<? extends SecurityProvider> clazz;
+            try {
+                clazz = (Class<? extends SecurityProvider>) 
Class.forName(className);
+            } catch (Exception e) {
+                String oldPackage = "brooklyn.web.console.security.";
+                if (className.startsWith(oldPackage)) {
+                    className = Strings.removeFromStart(className, oldPackage);
+                    className = 
DelegatingSecurityProvider.class.getPackage().getName() + "." + className;
+                    clazz = (Class<? extends SecurityProvider>) 
Class.forName(className);
+                    log.warn("Deprecated package " + oldPackage + " detected; 
please update security provider to point to " + className);
+                } else throw e;
+            }
+
+            Constructor<? extends SecurityProvider> constructor;
+            try {
+                constructor = clazz.getConstructor(ManagementContext.class);
+                delegate = constructor.newInstance(mgmt);
+            } catch (Exception e) {
+                constructor = clazz.getConstructor();
+                Object delegateO = constructor.newInstance();
+                if (!(delegateO instanceof SecurityProvider)) {
+                    // if classloaders get mangled it will be a different CL's 
SecurityProvider
+                    throw new ClassCastException("Delegate is either not a 
security provider or has an incompatible classloader: "+delegateO);
+                }
+                delegate = (SecurityProvider) delegateO;
+            }
+        } catch (Exception e) {
+            log.warn("REST unable to instantiate security provider " + 
className + "; all logins are being disallowed", e);
+            delegate = new BlackholeSecurityProvider();
+        }
+        
+        
((BrooklynProperties)mgmt.getConfig()).put(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE,
 delegate);
+        
+        return delegate;
+    }
+
+    /**
+     * Causes all existing sessions to be invalidated.
+     */
+    protected void invalidateExistingSessions() {
+        modCount.incrementAndGet();
+    }
+
+    @Override
+    public boolean isAuthenticated(HttpSession session) {
+        if (session == null) return false;
+        Object modCountWhenFirstAuthenticated = 
session.getAttribute(getModificationCountKey());
+        boolean authenticated = getDelegate().isAuthenticated(session) &&
+                
Long.valueOf(modCount.get()).equals(modCountWhenFirstAuthenticated);
+        return authenticated;
+    }
+
+    @Override
+    public boolean authenticate(HttpSession session, String user, String 
password) {
+        boolean authenticated = getDelegate().authenticate(session, user, 
password);
+        if (authenticated) {
+            session.setAttribute(getModificationCountKey(), modCount.get());
+        }
+        if (log.isTraceEnabled() && authenticated) {
+            log.trace("User {} authenticated with provider {}", user, 
getDelegate());
+        } else if (!authenticated && log.isDebugEnabled()) {
+            log.debug("Failed authentication for user {} with provider {}", 
user, getDelegate());
+        }
+        return authenticated;
+    }
+
+    @Override
+    public boolean logout(HttpSession session) { 
+        boolean logout = getDelegate().logout(session);
+        if (logout) {
+            session.removeAttribute(getModificationCountKey());
+        }
+        return logout;
+    }
+
+    private String getModificationCountKey() {
+        return getClass().getName() + ".ModCount";
+    }
+}

Reply via email to