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 { + +}