You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/08/09 04:55:20 UTC
[13/28] incubator-brooklyn git commit: brooklyn-rest-server: add
org.apache package prefix
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java b/usage/rest-server/src/main/java/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java
deleted file mode 100644
index dc033bc..0000000
--- a/usage/rest-server/src/main/java/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package brooklyn.rest.util.json;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Member;
-import java.lang.reflect.Method;
-
-import org.codehaus.jackson.annotate.JsonAutoDetect;
-import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
-import org.codehaus.jackson.annotate.JsonMethod;
-import org.codehaus.jackson.map.introspect.AnnotatedField;
-import org.codehaus.jackson.map.introspect.AnnotatedMember;
-import org.codehaus.jackson.map.introspect.AnnotatedMethod;
-import org.codehaus.jackson.map.introspect.VisibilityChecker;
-
-/** a visibility checker which disables getters, but allows private access,
- * unless {@link BidiSerialization#isStrictSerialization()} is enabled in which case public fields or annotations must be used.
- * <p>
- * the reason for this change to visibility
- * is that getters might generate a copy, resulting in infinite loops, whereas field access should never do so.
- * (see e.g. test in {@link BrooklynJacksonSerializerTest} which uses a sensor+config object whose getTypeToken
- * causes infinite recursion)
- **/
-public class PossiblyStrictPreferringFieldsVisibilityChecker implements VisibilityChecker<PossiblyStrictPreferringFieldsVisibilityChecker> {
- VisibilityChecker<?>
- vizDefault = new VisibilityChecker.Std(Visibility.NONE, Visibility.NONE, Visibility.NONE, Visibility.ANY, Visibility.ANY),
- vizStrict = new VisibilityChecker.Std(Visibility.NONE, Visibility.NONE, Visibility.NONE, Visibility.PUBLIC_ONLY, Visibility.PUBLIC_ONLY);
-
- @Override public PossiblyStrictPreferringFieldsVisibilityChecker with(JsonAutoDetect ann) { throw new UnsupportedOperationException(); }
- @Override public PossiblyStrictPreferringFieldsVisibilityChecker with(Visibility v) { throw new UnsupportedOperationException(); }
- @Override public PossiblyStrictPreferringFieldsVisibilityChecker withVisibility(JsonMethod method, Visibility v) { throw new UnsupportedOperationException(); }
- @Override public PossiblyStrictPreferringFieldsVisibilityChecker withGetterVisibility(Visibility v) { throw new UnsupportedOperationException(); }
- @Override public PossiblyStrictPreferringFieldsVisibilityChecker withIsGetterVisibility(Visibility v) { throw new UnsupportedOperationException(); }
- @Override public PossiblyStrictPreferringFieldsVisibilityChecker withSetterVisibility(Visibility v) { throw new UnsupportedOperationException(); }
- @Override public PossiblyStrictPreferringFieldsVisibilityChecker withCreatorVisibility(Visibility v) { throw new UnsupportedOperationException(); }
- @Override public PossiblyStrictPreferringFieldsVisibilityChecker withFieldVisibility(Visibility v) { throw new UnsupportedOperationException(); }
-
- protected VisibilityChecker<?> viz() {
- return BidiSerialization.isStrictSerialization() ? vizStrict : vizDefault;
- }
-
- @Override public boolean isGetterVisible(Method m) {
- return viz().isGetterVisible(m);
- }
-
- @Override
- public boolean isGetterVisible(AnnotatedMethod m) {
- return isGetterVisible(m.getAnnotated());
- }
-
- @Override
- public boolean isIsGetterVisible(Method m) {
- return viz().isIsGetterVisible(m);
- }
-
- @Override
- public boolean isIsGetterVisible(AnnotatedMethod m) {
- return isIsGetterVisible(m.getAnnotated());
- }
-
- @Override
- public boolean isSetterVisible(Method m) {
- return viz().isSetterVisible(m);
- }
-
- @Override
- public boolean isSetterVisible(AnnotatedMethod m) {
- return isSetterVisible(m.getAnnotated());
- }
-
- @Override
- public boolean isCreatorVisible(Member m) {
- return viz().isCreatorVisible(m);
- }
-
- @Override
- public boolean isCreatorVisible(AnnotatedMember m) {
- return isCreatorVisible(m.getMember());
- }
-
- @Override
- public boolean isFieldVisible(Field f) {
- return viz().isFieldVisible(f);
- }
-
- @Override
- public boolean isFieldVisible(AnnotatedField f) {
- return isFieldVisible(f.getAnnotated());
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
new file mode 100644
index 0000000..2e38666
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import brooklyn.rest.apidoc.ApidocHelpMessageBodyWriter;
+import org.apache.brooklyn.rest.resources.AbstractBrooklynRestResource;
+import org.apache.brooklyn.rest.resources.AccessResource;
+import org.apache.brooklyn.rest.resources.ActivityResource;
+import org.apache.brooklyn.rest.resources.ApidocResource;
+import org.apache.brooklyn.rest.resources.ApplicationResource;
+import org.apache.brooklyn.rest.resources.CatalogResource;
+import org.apache.brooklyn.rest.resources.EffectorResource;
+import org.apache.brooklyn.rest.resources.EntityConfigResource;
+import org.apache.brooklyn.rest.resources.EntityResource;
+import org.apache.brooklyn.rest.resources.LocationResource;
+import org.apache.brooklyn.rest.resources.PolicyConfigResource;
+import org.apache.brooklyn.rest.resources.PolicyResource;
+import org.apache.brooklyn.rest.resources.ScriptResource;
+import org.apache.brooklyn.rest.resources.SensorResource;
+import org.apache.brooklyn.rest.resources.ServerResource;
+import org.apache.brooklyn.rest.resources.UsageResource;
+import org.apache.brooklyn.rest.resources.VersionResource;
+import org.apache.brooklyn.rest.util.DefaultExceptionMapper;
+import org.apache.brooklyn.rest.util.FormMapProvider;
+import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
+
+import com.google.common.collect.Iterables;
+
+@SuppressWarnings("deprecation")
+public class BrooklynRestApi {
+
+ public static Iterable<AbstractBrooklynRestResource> getBrooklynRestResources() {
+ List<AbstractBrooklynRestResource> resources = new ArrayList<AbstractBrooklynRestResource>();
+ resources.add(new LocationResource());
+ resources.add(new CatalogResource());
+ resources.add(new ApplicationResource());
+ resources.add(new EntityResource());
+ resources.add(new EntityConfigResource());
+ resources.add(new SensorResource());
+ resources.add(new EffectorResource());
+ resources.add(new PolicyResource());
+ resources.add(new PolicyConfigResource());
+ resources.add(new ActivityResource());
+ resources.add(new AccessResource());
+ resources.add(new ScriptResource());
+ resources.add(new ServerResource());
+ resources.add(new UsageResource());
+ resources.add(new VersionResource());
+ return resources;
+ }
+
+ public static Iterable<Object> getApidocResources() {
+ List<Object> resources = new ArrayList<Object>();
+ resources.add(new ApidocHelpMessageBodyWriter());
+ resources.add(new ApidocResource());
+ return resources;
+ }
+
+ public static Iterable<Object> getMiscResources() {
+ List<Object> resources = new ArrayList<Object>();
+ resources.add(new DefaultExceptionMapper());
+ resources.add(new BrooklynJacksonJsonProvider());
+ resources.add(new FormMapProvider());
+ return resources;
+ }
+
+ public static Iterable<Object> getAllResources() {
+ return Iterables.concat(getBrooklynRestResources(), getApidocResources(), getMiscResources());
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
new file mode 100644
index 0000000..aaabaac
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
@@ -0,0 +1,155 @@
+/*
+ * 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;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.config.ConfigMap;
+import brooklyn.config.ConfigPredicates;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.location.PortRange;
+import org.apache.brooklyn.rest.security.provider.DelegatingSecurityProvider;
+import org.apache.brooklyn.rest.security.provider.ExplicitUsersSecurityProvider;
+
+public class BrooklynWebConfig {
+
+ public final static String BASE_NAME = "brooklyn.webconsole";
+ public final static String BASE_NAME_SECURITY = BASE_NAME+".security";
+
+ /**
+ * The security provider to be loaded by {@link DelegatingSecurityProvider}.
+ * e.g. <code>brooklyn.webconsole.security.provider=org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider</code>
+ * will allow anyone to log in.
+ */
+ public final static ConfigKey<String> SECURITY_PROVIDER_CLASSNAME = ConfigKeys.newStringConfigKey(
+ BASE_NAME_SECURITY+".provider", "class name of a Brooklyn SecurityProvider",
+ ExplicitUsersSecurityProvider.class.getCanonicalName());
+
+ /**
+ * Explicitly set the users/passwords, e.g. in brooklyn.properties:
+ * brooklyn.webconsole.security.users=admin,bob
+ * brooklyn.webconsole.security.user.admin.password=password
+ * brooklyn.webconsole.security.user.bob.password=bobspass
+ */
+ public final static ConfigKey<String> USERS = ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY+".users");
+
+ public final static ConfigKey<String> PASSWORD_FOR_USER(String user) {
+ return ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY + ".user." + user + ".password");
+ }
+
+ public final static ConfigKey<String> SALT_FOR_USER(String user) {
+ return ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY + ".user." + user + ".salt");
+ }
+
+ public final static ConfigKey<String> SHA256_FOR_USER(String user) {
+ return ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY + ".user." + user + ".sha256");
+ }
+
+ public final static ConfigKey<String> LDAP_URL = ConfigKeys.newStringConfigKey(
+ BASE_NAME_SECURITY+".ldap.url");
+
+ public final static ConfigKey<String> LDAP_REALM = ConfigKeys.newStringConfigKey(
+ BASE_NAME_SECURITY+".ldap.realm");
+
+ public final static ConfigKey<String> LDAP_OU = ConfigKeys.newStringConfigKey(
+ BASE_NAME_SECURITY+"ldap.ou");
+
+ public final static ConfigKey<Boolean> HTTPS_REQUIRED = ConfigKeys.newBooleanConfigKey(
+ BASE_NAME+".security.https.required",
+ "Whether HTTPS is required; false here can be overridden by CLI option", false);
+
+ public final static ConfigKey<PortRange> WEB_CONSOLE_PORT = ConfigKeys.newConfigKey(PortRange.class,
+ BASE_NAME+".port",
+ "Port/range for the web console to listen on; can be overridden by CLI option");
+
+ public final static ConfigKey<String> KEYSTORE_URL = ConfigKeys.newStringConfigKey(
+ BASE_NAME+".security.keystore.url",
+ "Keystore from which to take the certificate to present when running HTTPS; "
+ + "note that normally the password is also required, and an alias for the certificate if the keystore has more than one");
+
+ public final static ConfigKey<String> KEYSTORE_PASSWORD = ConfigKeys.newStringConfigKey(
+ BASE_NAME+".security.keystore.password",
+ "Password for the "+KEYSTORE_URL);
+
+ public final static ConfigKey<String> KEYSTORE_CERTIFICATE_ALIAS = ConfigKeys.newStringConfigKey(
+ BASE_NAME+".security.keystore.certificate.alias",
+ "Alias in "+KEYSTORE_URL+" for the certificate to use; defaults to the first if not supplied");
+
+ public final static ConfigKey<String> TRANSPORT_PROTOCOLS = ConfigKeys.newStringConfigKey(
+ BASE_NAME+".security.transport.protocols",
+ "SSL/TLS protocol versions to use for web console connections",
+ "TLSv1, TLSv1.1, TLSv1.2");
+
+ // https://wiki.mozilla.org/Security/Server_Side_TLS (v3.4)
+ // http://stackoverflow.com/questions/19846020/how-to-map-a-openssls-cipher-list-to-java-jsse
+ // list created on 05.05.2015, Intermediate config from first link
+ public final static ConfigKey<String> TRANSPORT_CIPHERS = ConfigKeys.newStringConfigKey(
+ BASE_NAME+".security.transport.ciphers",
+ "SSL/TLS cipher suites to use for web console connections",
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256," +
+ "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384," +
+ "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_DSS_WITH_AES_128_GCM_SHA256," +
+ "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384," +
+ "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256," +
+ "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA," +
+ "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384," +
+ "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA," +
+ "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA," +
+ "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256," +
+ "TLS_DHE_DSS_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA," +
+ "TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384," +
+ "TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256," +
+ "TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA," +
+ "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA," +
+ "TLS_SRP_SHA_WITH_AES_256_CBC_SHA,TLS_DHE_DSS_WITH_AES_256_CBC_SHA256," +
+ "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA," +
+ "TLS_SRP_SHA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA," +
+ "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA," +
+ "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA," +
+ "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,TLS_RSA_WITH_CAMELLIA_128_CBC_SHA," +
+ "TLS_RSA_WITH_3DES_EDE_CBC_SHA," +
+ // Same as above but with SSL_ prefix, IBM Java compatibility (cipher is independent of protocol)
+ // https://www-01.ibm.com/support/knowledgecenter/SSYKE2_7.0.0/com.ibm.java.security.component.70.doc/security-component/jsse2Docs/ciphersuites.html
+ "SSL_ECDHE_RSA_WITH_AES_128_GCM_SHA256,SSL_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256," +
+ "SSL_ECDHE_RSA_WITH_AES_256_GCM_SHA384,SSL_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384," +
+ "SSL_DHE_RSA_WITH_AES_128_GCM_SHA256,SSL_DHE_DSS_WITH_AES_128_GCM_SHA256," +
+ "SSL_DHE_DSS_WITH_AES_256_GCM_SHA384,SSL_DHE_RSA_WITH_AES_256_GCM_SHA384," +
+ "SSL_ECDHE_RSA_WITH_AES_128_CBC_SHA256,SSL_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256," +
+ "SSL_ECDHE_RSA_WITH_AES_128_CBC_SHA,SSL_ECDHE_ECDSA_WITH_AES_128_CBC_SHA," +
+ "SSL_ECDHE_RSA_WITH_AES_256_CBC_SHA384,SSL_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384," +
+ "SSL_ECDHE_RSA_WITH_AES_256_CBC_SHA,SSL_ECDHE_ECDSA_WITH_AES_256_CBC_SHA," +
+ "SSL_DHE_RSA_WITH_AES_128_CBC_SHA256,SSL_DHE_RSA_WITH_AES_128_CBC_SHA," +
+ "SSL_DHE_DSS_WITH_AES_128_CBC_SHA256,SSL_DHE_RSA_WITH_AES_256_CBC_SHA256," +
+ "SSL_DHE_DSS_WITH_AES_256_CBC_SHA,SSL_DHE_RSA_WITH_AES_256_CBC_SHA," +
+ "SSL_RSA_WITH_AES_128_GCM_SHA256,SSL_RSA_WITH_AES_256_GCM_SHA384," +
+ "SSL_RSA_WITH_AES_128_CBC_SHA256,SSL_RSA_WITH_AES_256_CBC_SHA256," +
+ "SSL_RSA_WITH_AES_128_CBC_SHA,SSL_RSA_WITH_AES_256_CBC_SHA," +
+ "SSL_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,SSL_SRP_SHA_RSA_WITH_AES_256_CBC_SHA," +
+ "SSL_SRP_SHA_WITH_AES_256_CBC_SHA,SSL_DHE_DSS_WITH_AES_256_CBC_SHA256," +
+ "SSL_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,SSL_SRP_SHA_RSA_WITH_AES_128_CBC_SHA," +
+ "SSL_SRP_SHA_WITH_AES_128_CBC_SHA,SSL_DHE_DSS_WITH_AES_128_CBC_SHA," +
+ "SSL_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,SSL_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA," +
+ "SSL_RSA_WITH_CAMELLIA_256_CBC_SHA,SSL_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA," +
+ "SSL_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,SSL_RSA_WITH_CAMELLIA_128_CBC_SHA," +
+ "SSL_RSA_WITH_3DES_EDE_CBC_SHA");
+
+ public final static boolean hasNoSecurityOptions(ConfigMap config) {
+ return config.submap(ConfigPredicates.startingWith(BASE_NAME_SECURITY)).isEmpty();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java
new file mode 100644
index 0000000..368e887
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java
@@ -0,0 +1,176 @@
+/*
+ * 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.filter;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.BrooklynServiceAttributes;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.entitlement.Entitlements;
+import brooklyn.management.entitlement.WebEntitlementContext;
+import org.apache.brooklyn.rest.security.provider.DelegatingSecurityProvider;
+import brooklyn.util.text.Strings;
+
+import com.sun.jersey.core.util.Base64;
+
+/**
+ * Provides basic HTTP authentication.
+ */
+public class BrooklynPropertiesSecurityFilter implements Filter {
+
+ /**
+ * The session attribute set for authenticated users; for reference
+ * (but should not be relied up to confirm authentication, as
+ * the providers may impose additional criteria such as timeouts,
+ * or a null user (no login) may be permitted)
+ */
+ public static final String AUTHENTICATED_USER_SESSION_ATTRIBUTE = "brooklyn.user";
+
+ /**
+ * The session attribute set to indicate the remote address of the HTTP request.
+ * Corresponds to {@link javax.servlet.http.HttpServletRequest#getRemoteAddr()}.
+ */
+ public static final String REMOTE_ADDRESS_SESSION_ATTRIBUTE = "request.remoteAddress";
+
+ private static final Logger log = LoggerFactory.getLogger(BrooklynPropertiesSecurityFilter.class);
+
+ protected DelegatingSecurityProvider provider;
+
+ private static ThreadLocal<String> originalRequest = new ThreadLocal<String>();
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+ String uri = httpRequest.getRequestURI();
+
+ if (provider == null) {
+ log.warn("No security provider available: disallowing web access to brooklyn");
+ httpResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+ return;
+ }
+
+ if (originalRequest.get() != null) {
+ // clear the entitlement context before setting to avoid warnings
+ Entitlements.clearEntitlementContext();
+ } else {
+ originalRequest.set(uri);
+ }
+
+ boolean authenticated = provider.isAuthenticated(httpRequest.getSession());
+ if ("/logout".equals(uri) || "/v1/logout".equals(uri)) {
+ httpResponse.setHeader("WWW-Authenticate", "Basic realm=\"brooklyn\"");
+ if (authenticated && httpRequest.getSession().getAttributeNames().hasMoreElements()) {
+ logout(httpRequest);
+ httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ } else {
+ RequestDispatcher dispatcher = httpRequest.getRequestDispatcher("/");
+ log.debug("Not authenticated, forwarding request for {} to {}", uri, dispatcher);
+ dispatcher.forward(httpRequest, httpResponse);
+ }
+ return;
+ }
+
+ if (!(httpRequest.getSession().getAttributeNames().hasMoreElements() && provider.isAuthenticated(httpRequest.getSession())) ||
+ "/logout".equals(originalRequest.get())) {
+ authenticated = authenticate(httpRequest);
+ }
+
+ if (!authenticated) {
+ httpResponse.setHeader("WWW-Authenticate", "Basic realm=\"brooklyn\"");
+ httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return;
+ }
+
+ // Note that the attribute AUTHENTICATED_USER_SESSION_ATTRIBUTE is only set in the call to authenticate(httpRequest),
+ // so must not try to get the user until that is done.
+ String uid = RequestTaggingFilter.getTag();
+ String user = Strings.toString(httpRequest.getSession().getAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE));
+ try {
+ WebEntitlementContext entitlementContext = new WebEntitlementContext(user, httpRequest.getRemoteAddr(), uri, uid);
+ Entitlements.setEntitlementContext(entitlementContext);
+
+ chain.doFilter(request, response);
+ } catch (Throwable e) {
+ if (!response.isCommitted()) {
+ httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+ } finally {
+ originalRequest.remove();
+ Entitlements.clearEntitlementContext();
+ }
+ }
+
+ protected boolean authenticate(HttpServletRequest request) {
+ HttpSession session = request.getSession();
+ if (provider.isAuthenticated(session)) {
+ return true;
+ }
+ session.setAttribute(REMOTE_ADDRESS_SESSION_ATTRIBUTE, request.getRemoteAddr());
+ String user = null, pass = null;
+ String authorization = request.getHeader("Authorization");
+ if (authorization != null) {
+ String userpass = Base64.base64Decode(authorization.substring(6));
+ user = userpass.substring(0, userpass.indexOf(":"));
+ pass = userpass.substring(userpass.indexOf(":") + 1);
+ }
+ if (provider.authenticate(session, user, pass)) {
+ if (user != null) {
+ session.setAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE, user);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void init(FilterConfig config) throws ServletException {
+ ManagementContext mgmt = (ManagementContext) config.getServletContext().getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
+ provider = new DelegatingSecurityProvider(mgmt);
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ protected void logout(HttpServletRequest request) {
+ log.info("REST logging {} out of session {}",
+ request.getSession().getAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE), request.getSession().getId());
+ provider.logout(request.getSession());
+ request.getSession().removeAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE);
+ request.getSession().removeAttribute(REMOTE_ADDRESS_SESSION_ATTRIBUTE);
+ request.getSession().invalidate();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
new file mode 100644
index 0000000..592a354
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
@@ -0,0 +1,151 @@
+/*
+ * 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.filter;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.management.ManagementContext;
+import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.rest.domain.ApiError;
+import brooklyn.util.text.Strings;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
+import com.sun.jersey.api.model.AbstractMethod;
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerRequestFilter;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+import com.sun.jersey.spi.container.ResourceFilter;
+import com.sun.jersey.spi.container.ResourceFilterFactory;
+
+/**
+ * Checks that if the method or resource class corresponding to a request
+ * has a {@link HaHotStateRequired} annotation,
+ * that the server is in that state (and up).
+ * Requests with {@link #SKIP_CHECK_HEADER} set as a header skip this check.
+ * <p>
+ * This follows a different pattern to {@link HaMasterCheckFilter}
+ * as this needs to know the method being invoked.
+ */
+public class HaHotCheckResourceFilter implements ResourceFilterFactory {
+
+ private static final Logger log = LoggerFactory.getLogger(HaHotCheckResourceFilter.class);
+
+ private static final Set<ManagementNodeState> HOT_STATES = ImmutableSet.of(
+ ManagementNodeState.MASTER, ManagementNodeState.HOT_STANDBY, ManagementNodeState.HOT_BACKUP);
+
+ @Context
+ private ManagementContext mgmt;
+
+ public HaHotCheckResourceFilter() {}
+
+ @VisibleForTesting
+ public HaHotCheckResourceFilter(ManagementContext mgmt) {
+ this.mgmt = mgmt;
+ }
+
+ private static class MethodFilter implements ResourceFilter, ContainerRequestFilter {
+
+ private AbstractMethod am;
+ private ManagementContext mgmt;
+
+ public MethodFilter(AbstractMethod am, ManagementContext mgmt) {
+ this.am = am;
+ this.mgmt = mgmt;
+ }
+
+ @Override
+ public ContainerRequestFilter getRequestFilter() {
+ return this;
+ }
+
+ @Override
+ public ContainerResponseFilter getResponseFilter() {
+ return null;
+ }
+
+ private String lookForProblem(ContainerRequest request) {
+ if (isSkipCheckHeaderSet(request))
+ return null;
+
+ if (!isHaHotStateRequired(request))
+ return null;
+
+ String problem = HaMasterCheckFilter.lookForProblemIfServerNotRunning(mgmt);
+ if (Strings.isNonBlank(problem))
+ return problem;
+
+ if (!isHaHotStatus())
+ return "server not in required HA hot state";
+ if (isStateNotYetValid())
+ return "server not yet completed loading data for required HA hot state";
+
+ return null;
+ }
+
+ @Override
+ public ContainerRequest filter(ContainerRequest request) {
+ String problem = lookForProblem(request);
+ if (Strings.isNonBlank(problem)) {
+ log.warn("Disallowing web request as "+problem+": "+request+"/"+am+" (caller should set '"+HaMasterCheckFilter.SKIP_CHECK_HEADER+"' to force)");
+ throw new WebApplicationException(ApiError.builder()
+ .message("This request is only permitted against an active hot Brooklyn server")
+ .errorCode(Response.Status.FORBIDDEN).build().asJsonResponse());
+ }
+ return request;
+ }
+
+ // Maybe there should be a separate state to indicate that we have switched state
+ // but still haven't finished rebinding. (Previously there was a time delay and an
+ // isRebinding check, but introducing RebindManager#isAwaitingInitialRebind() seems cleaner.)
+ private boolean isStateNotYetValid() {
+ return mgmt.getRebindManager().isAwaitingInitialRebind();
+ }
+
+ private boolean isHaHotStateRequired(ContainerRequest request) {
+ return (am.getAnnotation(HaHotStateRequired.class) != null ||
+ am.getResource().getAnnotation(HaHotStateRequired.class) != null);
+ }
+
+ private boolean isSkipCheckHeaderSet(ContainerRequest request) {
+ return "true".equalsIgnoreCase(request.getHeaderValue(HaMasterCheckFilter.SKIP_CHECK_HEADER));
+ }
+
+ private boolean isHaHotStatus() {
+ ManagementNodeState state = mgmt.getHighAvailabilityManager().getNodeState();
+ return HOT_STATES.contains(state);
+ }
+
+ }
+
+ @Override
+ public List<ResourceFilter> create(AbstractMethod am) {
+ return Collections.<ResourceFilter>singletonList(new MethodFilter(am, mgmt));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotStateRequired.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotStateRequired.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotStateRequired.java
new file mode 100644
index 0000000..f64abdd
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotStateRequired.java
@@ -0,0 +1,36 @@
+/*
+ * 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.filter;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * When a REST method (or its containing class) is marked with this annotation
+ * requests to it will fail with a 403 response if the instance is not in MASTER
+ * mode (or has recently switched or is still rebinding). Guards the method so
+ * that when it returns the caller can be certain of the response. For example
+ * if the response is 404, then the resource doesn't exist as opposed to
+ * not being loaded from persistence store yet.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface HaHotStateRequired {}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java
new file mode 100644
index 0000000..861a7e6
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java
@@ -0,0 +1,140 @@
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import java.util.Set;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.BrooklynServiceAttributes;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import brooklyn.util.text.Strings;
+
+import com.google.common.collect.Sets;
+
+/**
+ * Checks that for requests that want HA master state, the server is up and in that state.
+ * <p>
+ * Post POSTs and PUTs are assumed to need master state, with the exception of shutdown.
+ * Requests with {@link #SKIP_CHECK_HEADER} set as a header skip this check.
+ */
+public class HaMasterCheckFilter implements Filter {
+
+ private static final Logger log = LoggerFactory.getLogger(HaMasterCheckFilter.class);
+
+ public static final String SKIP_CHECK_HEADER = "Brooklyn-Allow-Non-Master-Access";
+ private static final Set<String> SAFE_STANDBY_METHODS = Sets.newHashSet("GET", "HEAD");
+
+ protected ServletContext servletContext;
+ protected ManagementContext mgmt;
+
+ @Override
+ public void init(FilterConfig config) throws ServletException {
+ servletContext = config.getServletContext();
+ mgmt = (ManagementContext) servletContext.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
+ }
+
+ static String lookForProblemIfServerNotRunning(ManagementContext mgmt) {
+ if (mgmt==null) return "no management context available";
+ if (!mgmt.isRunning()) return "server no longer running";
+ if (!mgmt.isStartupComplete()) return "server not in required startup-completed state";
+ return null;
+ }
+
+ private String lookForProblem(ServletRequest request) {
+ if (isSkipCheckHeaderSet(request))
+ return null;
+
+ if (!isMasterRequiredForRequest(request))
+ return null;
+
+ String problem = lookForProblemIfServerNotRunning(mgmt);
+ if (Strings.isNonBlank(problem))
+ return problem;
+
+ if (!isMaster())
+ return "server not in required HA master state";
+
+ return null;
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ String problem = lookForProblem(request);
+ if (problem!=null) {
+ log.warn("Disallowing web request as "+problem+": "+request.getParameterMap()+" (caller should set '"+SKIP_CHECK_HEADER+"' to force)");
+ WebResourceUtils.applyJsonResponse(servletContext, ApiError.builder()
+ .message("This request is only permitted against an active master Brooklyn server")
+ .errorCode(Response.Status.FORBIDDEN).build().asJsonResponse(), (HttpServletResponse)response);
+ } else {
+ chain.doFilter(request, response);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ private boolean isMaster() {
+ return ManagementNodeState.MASTER.equals(mgmt.getHighAvailabilityManager().getNodeState());
+ }
+
+ private boolean isMasterRequiredForRequest(ServletRequest request) {
+ if (request instanceof HttpServletRequest) {
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+
+ String method = httpRequest.getMethod().toUpperCase();
+ // gets usually okay
+ if (SAFE_STANDBY_METHODS.contains(method)) return false;
+
+ // explicitly allow calls to shutdown
+ // (if stopAllApps is specified, the method itself will fail; but we do not want to consume parameters here, that breaks things!)
+ // TODO combine with HaHotCheckResourceFilter and use an annotation HaAnyStateAllowed or similar
+ if ("/v1/server/shutdown".equals(httpRequest.getRequestURI())) return false;
+
+ // master required for everything else
+ return true;
+ }
+ // previously non-HttpServletRequests were allowed but I don't think they should be
+ return true;
+ }
+
+ private boolean isSkipCheckHeaderSet(ServletRequest httpRequest) {
+ if (httpRequest instanceof HttpServletRequest)
+ return "true".equalsIgnoreCase(((HttpServletRequest)httpRequest).getHeader(SKIP_CHECK_HEADER));
+ return false;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/LoggingFilter.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/LoggingFilter.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/LoggingFilter.java
new file mode 100644
index 0000000..96eaecb
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/LoggingFilter.java
@@ -0,0 +1,160 @@
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.Sets;
+
+import brooklyn.config.BrooklynLogging;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.time.Duration;
+
+/**
+ * Handles logging of request information.
+ */
+public class LoggingFilter implements Filter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(BrooklynLogging.REST);
+
+ /** Methods logged at trace. */
+ private static final Set<String> UNINTERESTING_METHODS = Sets.newHashSet("GET", "HEAD");
+
+ /** Headers whose values will not be logged. */
+ private static final Set<String> CENSORED_HEADERS = Sets.newHashSet("Authorization");
+
+ /** Log all requests that take this time or longer to complete. */
+ private static final Duration REQUEST_DURATION_LOG_POINT = Duration.FIVE_SECONDS;
+
+ public void init(FilterConfig config) throws ServletException {
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+ String rid = RequestTaggingFilter.getTag();
+ boolean isInteresting = !UNINTERESTING_METHODS.contains(httpRequest.getMethod().toUpperCase());
+ boolean shouldLog = (isInteresting && LOG.isDebugEnabled()) || LOG.isTraceEnabled();
+ boolean requestErrored = false;
+ if (shouldLog) {
+ String message = "Request {} starting: {} {} from {}";
+ Object[] args = new Object[]{rid, httpRequest.getMethod(), httpRequest.getRequestURI(), httpRequest.getRemoteAddr()};
+ if (isInteresting) {
+ LOG.debug(message, args);
+ } else {
+ LOG.trace(message, args);
+ }
+ }
+
+ Stopwatch timer = Stopwatch.createStarted();
+ try {
+ chain.doFilter(request, response);
+ } catch (Throwable e) {
+ requestErrored = true;
+ isInteresting = true;
+ LOG.warn("Request " + rid + " ("+httpRequest.getMethod()+" "+httpRequest.getRequestURI()+" from "+httpRequest.getRemoteAddr()+") failed: " + e, e);
+ // Propagate for handling by other filter
+ throw Exceptions.propagate(e);
+ } finally {
+ timer.stop();
+ // This logging must not happen before chain.doFilter, or FormMapProvider will not work as expected.
+ // Getting the parameter map consumes the request body and only resource methods using @FormParam
+ // will work as expected.
+ isInteresting |= (timer.elapsed(TimeUnit.SECONDS) - REQUEST_DURATION_LOG_POINT.toSeconds()) > 0;
+ if (shouldLog) {
+ boolean includeHeaders = requestErrored || httpResponse.getStatus() / 100 == 5 || LOG.isTraceEnabled();
+ String message = getRequestCompletedMessage(includeHeaders, Duration.of(timer), rid, httpRequest, httpResponse);
+ if (requestErrored || isInteresting) {
+ LOG.debug(message);
+ } else {
+ LOG.trace(message);
+ }
+ }
+ }
+ }
+
+ private String getRequestCompletedMessage(boolean includeHeaders, Duration elapsed,
+ String id, HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
+ StringBuilder message = new StringBuilder("Request ")
+ .append(id)
+ .append(" completed in ")
+ .append(elapsed)
+ .append(": response ")
+ .append(httpResponse.getStatus())
+ .append(" for ")
+ .append(httpRequest.getMethod())
+ .append(" ")
+ .append(httpRequest.getRequestURI())
+ .append(" from ")
+ .append(httpRequest.getRemoteAddr());
+
+ if (!httpRequest.getParameterMap().isEmpty()) {
+ message.append(", parameters: ")
+ .append(Joiner.on(", ").withKeyValueSeparator("=").join(httpRequest.getParameterMap()));
+ }
+ if (httpRequest.getContentLength() > 0) {
+ int len = httpRequest.getContentLength();
+ message.append(" contentType=").append(httpRequest.getContentType())
+ .append(" (length=").append(len).append(")");
+ }
+ if (includeHeaders) {
+ Enumeration<String> headerNames = httpRequest.getHeaderNames();
+ if (headerNames.hasMoreElements()) {
+ message.append(", headers: ");
+ while (headerNames.hasMoreElements()) {
+ String headerName = headerNames.nextElement();
+ message.append(headerName).append(": ");
+ if (CENSORED_HEADERS.contains(headerName)) {
+ message.append("******");
+ } else {
+ message.append(httpRequest.getHeader(headerName));
+ }
+ if (headerNames.hasMoreElements()) {
+ message.append(", ");
+ }
+ }
+ }
+ }
+
+ return message.toString();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
new file mode 100644
index 0000000..8a3c1c6
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.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.filter;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerResponse;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+
+public class NoCacheFilter implements ContainerResponseFilter {
+
+ @Override
+ public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
+ //https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ
+ MultivaluedMap<String, Object> headers = response.getHttpHeaders();
+ headers.putSingle(HttpHeaders.CACHE_CONTROL, "no-cache, no-store");
+ headers.putSingle("Pragma", "no-cache");
+ headers.putSingle(HttpHeaders.EXPIRES, "0");
+ return response;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/RequestTaggingFilter.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/RequestTaggingFilter.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/RequestTaggingFilter.java
new file mode 100644
index 0000000..5a21b8f
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/RequestTaggingFilter.java
@@ -0,0 +1,62 @@
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import brooklyn.util.text.Identifiers;
+
+/**
+ * Tags each request with a probabilistically unique id. Should be included before other
+ * filters to make sense.
+ */
+public class RequestTaggingFilter implements Filter {
+
+ private static ThreadLocal<String> tag = new ThreadLocal<String>();
+
+ protected static String getTag() {
+ return tag.get();
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ String requestId = Identifiers.makeRandomId(6);
+ tag.set(requestId);
+ try {
+ chain.doFilter(request, response);
+ } finally {
+ tag.remove();
+ }
+ }
+
+ @Override
+ public void init(FilterConfig config) throws ServletException {
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java
new file mode 100644
index 0000000..37a48e6
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java
@@ -0,0 +1,153 @@
+/*
+ * 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 io.brooklyn.camp.CampPlatform;
+
+import javax.annotation.Nullable;
+import javax.servlet.ServletContext;
+import javax.ws.rs.core.Context;
+
+import org.codehaus.jackson.map.ObjectMapper;
+
+import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.BrooklynServiceAttributes;
+import brooklyn.config.render.RendererHints;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.ManagementContextInjectable;
+import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.time.Duration;
+
+public abstract class AbstractBrooklynRestResource implements ManagementContextInjectable {
+
+ // can be injected by jersey when ManagementContext in not injected manually
+ // (seems there is no way to make this optional so note it _must_ be injected;
+ // most of the time that happens for free, but with test framework it doesn't,
+ // so we have set up a NullServletContextProvider in our tests)
+ @Context ServletContext servletContext;
+
+ private ManagementContext managementContext;
+ private BrooklynRestResourceUtils brooklynRestResourceUtils;
+ private ObjectMapper mapper;
+
+ public ManagementContext mgmt() {
+ return mgmtMaybe().get();
+ }
+
+ protected synchronized Maybe<ManagementContext> mgmtMaybe() {
+ if (managementContext!=null) return Maybe.of(managementContext);
+ managementContext = (ManagementContext) servletContext.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
+ if (managementContext!=null) return Maybe.of(managementContext);
+
+ return Maybe.absent("ManagementContext not available for Brooklyn Jersey Resource "+this);
+ }
+
+ public void injectManagementContext(ManagementContext managementContext) {
+ if (this.managementContext!=null) {
+ if (this.managementContext.equals(managementContext)) return;
+ throw new IllegalStateException("ManagementContext cannot be changed: specified twice for Brooklyn Jersey Resource "+this);
+ }
+ this.managementContext = managementContext;
+ }
+
+ public synchronized BrooklynRestResourceUtils brooklyn() {
+ if (brooklynRestResourceUtils!=null) return brooklynRestResourceUtils;
+ brooklynRestResourceUtils = new BrooklynRestResourceUtils(mgmt());
+ return brooklynRestResourceUtils;
+ }
+
+ protected ObjectMapper mapper() {
+ if (mapper==null)
+ mapper = BrooklynJacksonJsonProvider.findAnyObjectMapper(servletContext, managementContext);
+ return mapper;
+ }
+
+ /** @deprecated since 0.7.0 use {@link #getValueForDisplay(Object, boolean, boolean, Boolean, EntityLocal, Duration)} */ @Deprecated
+ protected Object getValueForDisplay(Object value, boolean preferJson, boolean isJerseyReturnValue) {
+ return resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(isJerseyReturnValue).resolve();
+ }
+
+ protected RestValueResolver resolving(Object v) {
+ return new RestValueResolver(v).mapper(mapper());
+ }
+
+ public static class RestValueResolver {
+ final private Object valueToResolve;
+ private @Nullable ObjectMapper mapper;
+ private boolean preferJson;
+ private boolean isJerseyReturnValue;
+ private @Nullable Boolean raw;
+ private @Nullable Entity entity;
+ private @Nullable Duration timeout;
+ private @Nullable Object rendererHintSource;
+
+ public static RestValueResolver resolving(Object v) { return new RestValueResolver(v); }
+
+ private RestValueResolver(Object v) { valueToResolve = v; }
+
+ public RestValueResolver mapper(ObjectMapper mapper) { this.mapper = mapper; return this; }
+
+ /** whether JSON is the ultimate product;
+ * main effect here is to give null for null if true, else to give empty string
+ * <p>
+ * conversion to JSON for complex types is done subsequently (often by the framework)
+ * <p>
+ * default is true */
+ public RestValueResolver preferJson(boolean preferJson) { this.preferJson = preferJson; return this; }
+ /** whether an outermost string must be wrapped in quotes, because a String return object is treated as
+ * already JSON-encoded
+ * <p>
+ * default is false */
+ public RestValueResolver asJerseyOutermostReturnValue(boolean asJerseyReturnJson) {
+ isJerseyReturnValue = asJerseyReturnJson;
+ return this;
+ }
+ public RestValueResolver raw(Boolean raw) { this.raw = raw; return this; }
+ public RestValueResolver context(Entity entity) { this.entity = entity; return this; }
+ public RestValueResolver timeout(Duration timeout) { this.timeout = timeout; return this; }
+ public RestValueResolver renderAs(Object rendererHintSource) { this.rendererHintSource = rendererHintSource; return this; }
+
+ public Object resolve() {
+ Object valueResult = getImmediateValue(valueToResolve, entity);
+ if (valueResult==UNRESOLVED) valueResult = valueToResolve;
+ if (rendererHintSource!=null && Boolean.FALSE.equals(raw)) {
+ valueResult = RendererHints.applyDisplayValueHintUnchecked(rendererHintSource, valueResult);
+ }
+ return WebResourceUtils.getValueForDisplay(mapper, valueResult, preferJson, isJerseyReturnValue);
+ }
+
+ private static Object UNRESOLVED = "UNRESOLVED".toCharArray();
+
+ private static Object getImmediateValue(Object value, @Nullable Entity context) {
+ return Tasks.resolving(value).as(Object.class).defaultValue(UNRESOLVED).timeout(Duration.ZERO).context(context).swallowExceptions().get();
+ }
+
+ }
+
+ protected CampPlatform camp() {
+ return BrooklynServerConfig.getCampPlatform(mgmt()).get();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AccessResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AccessResource.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AccessResource.java
new file mode 100644
index 0000000..a948254
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AccessResource.java
@@ -0,0 +1,46 @@
+/*
+ * 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 javax.ws.rs.core.Response;
+
+import brooklyn.management.internal.AccessManager;
+import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.rest.api.AccessApi;
+import brooklyn.rest.domain.AccessSummary;
+import org.apache.brooklyn.rest.transform.AccessTransformer;
+
+import com.google.common.annotations.Beta;
+
+@Beta
+public class AccessResource extends AbstractBrooklynRestResource implements AccessApi {
+
+ @Override
+ public AccessSummary get() {
+ AccessManager accessManager = ((ManagementContextInternal) mgmt()).getAccessManager();
+ return AccessTransformer.accessSummary(accessManager);
+ }
+
+ @Override
+ public Response locationProvisioningAllowed(boolean allowed) {
+ AccessManager accessManager = ((ManagementContextInternal) mgmt()).getAccessManager();
+ accessManager.setLocationProvisioningAllowed(allowed);
+ return Response.status(Response.Status.OK).build();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ActivityResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ActivityResource.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ActivityResource.java
new file mode 100644
index 0000000..53a49d3
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ActivityResource.java
@@ -0,0 +1,67 @@
+/*
+ * 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.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.BrooklynTaskTags.WrappedStream;
+import brooklyn.management.HasTaskChildren;
+import brooklyn.management.Task;
+import brooklyn.rest.api.ActivityApi;
+import brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.transform.TaskTransformer;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
+
+public class ActivityResource extends AbstractBrooklynRestResource implements ActivityApi {
+
+ @Override
+ public TaskSummary get(String taskId) {
+ Task<?> t = mgmt().getExecutionManager().getTask(taskId);
+ if (t == null)
+ throw WebResourceUtils.notFound("Cannot find task '%s'", taskId);
+ return TaskTransformer.FROM_TASK.apply(t);
+ }
+
+ @Override
+ public List<TaskSummary> children(String taskId) {
+ Task<?> t = mgmt().getExecutionManager().getTask(taskId);
+ if (t == null)
+ throw WebResourceUtils.notFound("Cannot find task '%s'", taskId);
+ if (!(t instanceof HasTaskChildren))
+ return Collections.emptyList();
+ return new LinkedList<TaskSummary>(Collections2.transform(Lists.newArrayList(((HasTaskChildren) t).getChildren()),
+ TaskTransformer.FROM_TASK));
+ }
+
+ public String stream(String taskId, String streamId) {
+ Task<?> t = mgmt().getExecutionManager().getTask(taskId);
+ if (t == null)
+ throw WebResourceUtils.notFound("Cannot find task '%s'", taskId);
+ WrappedStream stream = BrooklynTaskTags.stream(t, streamId);
+ if (stream == null)
+ throw WebResourceUtils.notFound("Cannot find stream '%s' in task '%s'", streamId, taskId);
+ return stream.streamContents.get();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
new file mode 100644
index 0000000..16292bd
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
@@ -0,0 +1,29 @@
+/*
+ * 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 brooklyn.rest.apidoc.Apidoc;
+
+import javax.ws.rs.Path;
+
+@Apidoc("API Documentation")
+@Path("/v1/apidoc")
+public class ApidocResource extends brooklyn.rest.apidoc.ApidocResource {
+
+}