You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by GitBox <gi...@apache.org> on 2019/01/16 02:11:48 UTC
[brooklyn-server] Diff for: [GitHub] asfgit merged pull request #1024:
Implement REST and UI security as filter not JAAS LoginModule, start OAuth
diff --git a/core/src/test/java/org/apache/brooklyn/core/test/HttpService.java b/core/src/test/java/org/apache/brooklyn/core/test/HttpService.java
index b2ff422f4e..dff408f1e0 100644
--- a/core/src/test/java/org/apache/brooklyn/core/test/HttpService.java
+++ b/core/src/test/java/org/apache/brooklyn/core/test/HttpService.java
@@ -28,10 +28,7 @@
import java.net.InetAddress;
import java.security.KeyStore;
-import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.eclipse.jetty.security.HashLoginService;
-import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.security.*;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
@@ -93,7 +90,9 @@ public HttpService(PortRange portRange, boolean httpsEnabled) {
*/
public HttpService basicAuthentication(String username, String password) {
HashLoginService l = new HashLoginService();
- l.putUser(username, Credential.getCredential(password), new String[]{"user"});
+ UserStore userStore = new UserStore();
+ userStore.addUser(username, Credential.getCredential(password), new String[]{"user"});
+ l.setUserStore(userStore);
l.setName("test-realm");
Constraint constraint = new Constraint(Constraint.__BASIC_AUTH, "user");
diff --git a/karaf/features/src/main/feature/feature.xml b/karaf/features/src/main/feature/feature.xml
index 42cf4e439f..de2d8961d0 100644
--- a/karaf/features/src/main/feature/feature.xml
+++ b/karaf/features/src/main/feature/feature.xml
@@ -207,6 +207,7 @@
<feature>cxf-jaxrs</feature>
<bundle dependency="true">mvn:org.apache.cxf/cxf-rt-rs-security-cors/${cxf.version}</bundle>
+ <bundle dependency="true">mvn:org.apache.cxf/cxf-rt-frontend-jaxrs/${cxf.version}</bundle>
<bundle dependency="true">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/${fasterxml.jackson.version}</bundle>
diff --git a/karaf/jetty-config/pom.xml b/karaf/jetty-config/pom.xml
index bd330b66d6..382a76a8f9 100644
--- a/karaf/jetty-config/pom.xml
+++ b/karaf/jetty-config/pom.xml
@@ -47,7 +47,6 @@
<Import-Package>
org.eclipse.jetty.jaas,
org.eclipse.jetty.security,
- org.apache.brooklyn.rest.security.jaas,
*
</Import-Package>
</instructions>
diff --git a/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java b/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
index 9ead29a4ab..7cb7df36fd 100644
--- a/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
+++ b/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
@@ -37,7 +37,6 @@
import java.util.Map;
import javax.annotation.Nullable;
-import javax.security.auth.spi.LoginModule;
import org.apache.brooklyn.api.location.PortRange;
import org.apache.brooklyn.api.mgmt.ManagementContext;
@@ -47,12 +46,14 @@
import org.apache.brooklyn.core.location.PortRanges;
import org.apache.brooklyn.core.mgmt.ShutdownHandler;
import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.server.BrooklynServerConfig;
import org.apache.brooklyn.core.server.BrooklynServerPaths;
import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
import org.apache.brooklyn.rest.BrooklynWebConfig;
import org.apache.brooklyn.rest.NopSecurityHandler;
import org.apache.brooklyn.rest.RestApiSetup;
+import org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilterJavax;
import org.apache.brooklyn.rest.filter.CorsImplSupplierFilter;
import org.apache.brooklyn.rest.filter.CsrfTokenFilter;
import org.apache.brooklyn.rest.filter.EntitlementContextFilter;
@@ -61,9 +62,7 @@
import org.apache.brooklyn.rest.filter.NoCacheFilter;
import org.apache.brooklyn.rest.filter.RequestTaggingFilter;
import org.apache.brooklyn.rest.filter.RequestTaggingRsFilter;
-import org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule;
-import org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule.RolePrincipal;
-import org.apache.brooklyn.rest.security.jaas.JaasUtils;
+import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
import org.apache.brooklyn.rest.util.ManagementContextProvider;
import org.apache.brooklyn.rest.util.ShutdownHandlerProvider;
import org.apache.brooklyn.util.collections.MutableMap;
@@ -88,7 +87,6 @@
import org.apache.brooklyn.util.time.Time;
import org.apache.brooklyn.util.web.ContextHandlerCollectionHotSwappable;
import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.jaas.JAASLoginService;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -200,14 +198,6 @@
private File webappTempDir;
- /**
- * @deprecated since 0.9.0, use {@link #consoleSecurity} to disable security or
- * register an alternative JAAS {@link LoginModule}.
- * {@link BrooklynLoginModule} used by default.
- */
- @Deprecated
- private Class<org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter> securityFilterClazz;
-
@SetFromFlag
private boolean skipSecurity = false;
@@ -230,7 +220,6 @@ public BrooklynWebServer(Map<?,?> flags, ManagementContext managementContext) {
log.warn("Ignoring unknown flags " + leftovers);
webappTempDir = BrooklynServerPaths.getBrooklynWebTmpDir(managementContext);
- JaasUtils.init(managementContext);
}
public BrooklynWebServer(ManagementContext managementContext, int port) {
@@ -241,12 +230,6 @@ public BrooklynWebServer(ManagementContext managementContext, int port, String w
this(MutableMap.of("port", port, "war", warUrl), managementContext);
}
- /** @deprecated since 0.9.0, use {@link #skipSecurity} or {@link BrooklynLoginModule} */
- @Deprecated
- public void setSecurityFilter(Class<org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter> filterClazz) {
- this.securityFilterClazz = filterClazz;
- }
-
public BrooklynWebServer skipSecurity() {
return skipSecurity(true);
}
@@ -397,13 +380,6 @@ public synchronized void start() throws Exception {
server = new Server(threadPool);
- // Can be moved to jetty-web.xml inside wars or a global jetty.xml.
- JAASLoginService loginService = new JAASLoginService();
- loginService.setName("webconsole");
- loginService.setLoginModuleName("webconsole");
- loginService.setRoleClassNames(new String[] {RolePrincipal.class.getName()});
- server.addBean(loginService);
-
final ServerConnector connector;
if (getHttpsEnabled()) {
@@ -500,10 +476,8 @@ private WebAppContext deployRestApi(WebAppContext context) {
providersListBuilder.build().toArray());
RestApiSetup.installServletFilters(context,
RequestTaggingFilter.class,
+ BrooklynSecurityProviderFilterJavax.class,
LoggingFilter.class);
- if (securityFilterClazz != null) {
- RestApiSetup.installServletFilters(context, securityFilterClazz);
- }
return context;
}
@@ -691,6 +665,18 @@ private void initSecurity(WebAppContext context) {
//
// Ignore security config in web.xml.
context.setSecurityHandler(new NopSecurityHandler());
+
+ // Above probably not used any more. Handled by the BrooklynSecurityProviderFilter* classes
+ String provider = Strings.toString( ((ManagementContextInternal)managementContext).getBrooklynProperties().getConfigRaw(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME).orNull() );
+ if (provider==null) {
+ ((ManagementContextInternal)managementContext).getBrooklynProperties().put(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME,
+ AnyoneSecurityProvider.class.getName());
+ } else if (AnyoneSecurityProvider.class.getName().equals(provider)) {
+ // already set
+ } else {
+ // mismatch - warn, but continue
+ log.warn("Server told to skip security with unexpected security provider: "+provider);
+ }
} else {
// Cover for downstream projects which don't have the changes.
context.addOverrideDescriptor(getClass().getResource("/web-security.xml").toExternalForm());
diff --git a/launcher/src/main/java/org/apache/brooklyn/launcher/WebAppContextProvider.java b/launcher/src/main/java/org/apache/brooklyn/launcher/WebAppContextProvider.java
index 840e54146e..6e269d1bf0 100644
--- a/launcher/src/main/java/org/apache/brooklyn/launcher/WebAppContextProvider.java
+++ b/launcher/src/main/java/org/apache/brooklyn/launcher/WebAppContextProvider.java
@@ -24,6 +24,8 @@
import java.io.InputStream;
import java.util.Map;
+import javax.servlet.Servlet;
+
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
import org.apache.brooklyn.launcher.config.CustomResourceLocator;
@@ -31,7 +33,7 @@
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.text.Identifiers;
-import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.webapp.WebAppContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -73,10 +75,12 @@ public WebAppContext get(ManagementContext managementContext, Map<String, Object
final WebAppContext context = new WebAppContext();
// use a unique session ID to prevent interference with other web apps on same server (esp for localhost);
- // it might be better to make this brooklyn-only or base on the management-plane ID;
- // but i think it actually *is* per-server instance, since we don't cache sessions server-side,
- // so i think this is write. [Alex 2015-09]
- context.setInitParameter(SessionManager.__SessionCookieProperty, SessionManager.__DefaultSessionCookie + "_" + "BROOKLYN" + Identifiers.makeRandomId(6));
+ // note however this is only run for the legacy launcher
+ // TODO would be nice if the various karaf startups rename the session cookie property (from JSESSIONID)
+ // as the default is likely to conflict with other java-based servers (esp on localhost);
+ // this can be done e.g. on ServletContext.getSessionCookieConfig(), but will be needed for REST and for JS (static) bundles
+ // low priority however, if you /etc/hosts a localhost-brooklyn and use that it will stop conflicting
+ context.setInitParameter(SessionHandler.__SessionCookieProperty, SessionHandler.__DefaultSessionCookie + "_" + "BROOKLYN" + Identifiers.makeRandomId(6));
context.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, managementContext);
for (Map.Entry<String, Object> attributeEntry : attributes.entrySet()) {
diff --git a/pom.xml b/pom.xml
index dedaf31586..20c0bc8670 100644
--- a/pom.xml
+++ b/pom.xml
@@ -117,7 +117,7 @@
<kxml2.servicemix.version>2.3.0_3</kxml2.servicemix.version>
<!-- double-check downstream projects before changing jackson version -->
<fasterxml.jackson.version>2.9.6</fasterxml.jackson.version>
- <cxf.version>3.1.10</cxf.version>
+ <cxf.version>3.2.7</cxf.version>
<httpcomponents.httpclient.version>4.5.2</httpcomponents.httpclient.version>
<httpcomponents.httpcore.version>4.4.4</httpcomponents.httpcore.version>
<!-- @deprecated since 0.11 -->
@@ -154,7 +154,7 @@
<aopalliance.version>1.0</aopalliance.version>
<commons-configuration.version>1.7</commons-configuration.version>
<commons-lang.version>2.4</commons-lang.version>
- <jax-rs-api.version>2.0.1</jax-rs-api.version>
+ <jax-rs-api.version>2.1.1</jax-rs-api.version>
<maxmind.version>2.8.0-rc1</maxmind.version>
<maxmind-db.version>1.2.1</maxmind-db.version>
<winrm4j.version>0.5.0</winrm4j.version>
@@ -162,10 +162,10 @@
<kubernetes-client.version>1.4.27</kubernetes-client.version>
<!-- Dependencies shipped with vanilla karaf; update these when we update the karaf version -->
- <karaf.version>4.2.1</karaf.version>
- <karaf.plugin.version>4.1.4</karaf.plugin.version><!-- held back for KARAF-5932 - fixed in 4.2.2 -->
+ <karaf.version>4.2.2</karaf.version>
+ <karaf.plugin.version>${karaf.version}</karaf.plugin.version>
<felix.framework.version>5.6.10</felix.framework.version>
- <jetty.version>9.3.24.v20180605</jetty.version>
+ <jetty.version>9.4.11.v20180605</jetty.version>
<!-- Transitive dependencies, declared explicitly to avoid version mismatch -->
<clojure.version>1.4.0</clojure.version>
@@ -175,7 +175,7 @@
<commons-logging.version>1.2</commons-logging.version>
<jsonSmart.version>2.3</jsonSmart.version>
<minidev.accessors-smart.version>1.2</minidev.accessors-smart.version>
- <ow2.asm.version>5.0.4</ow2.asm.version> <!-- from json-path:2.4.0 and cxf-rt-frontend-jaxws:3.1.10; clashes with 5.2 in karaf:4.1.6 -->
+ <ow2.asm.version>5.2</ow2.asm.version> <!-- version should align with cxf-rt-frontend-jaxws; see also json-path and karaf version requirements -->
<commons-beanutils.version>1.9.3</commons-beanutils.version>
<commons-collections.version>3.2.2</commons-collections.version>
<javax.mail.version>1.4.4</javax.mail.version>
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LogoutApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LogoutApi.java
index f0ca328e85..526a72f03a 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LogoutApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LogoutApi.java
@@ -36,19 +36,22 @@
@POST
@ApiOperation(value = "Request a logout and clean session")
@ApiResponses(value = {
- @ApiResponse(code = 307, message = "Redirect to /logout/user, keeping the request method")
+ @ApiResponse(code = 307, message = "Redirect to /logout/{user}, keeping the request method")
})
Response logout();
-
+ // TODO what is this for? misleading as it does not unauthorize the _session_ or log out in any way;
+ // deprecating as at 2019-01
+ /** @deprecated since 1.0 */
+ @Deprecated
@POST
@Path("/unauthorize")
- @ApiOperation(value = "Return UNAUTHORIZED 401 response")
+ @ApiOperation(value = "Return UNAUTHORIZED 401 response, but without disabling the session [deprecated]")
Response unAuthorize();
@POST
@Path("/{user}")
- @ApiOperation(value = "Logout and clean session if matching user logged")
+ @ApiOperation(value = "Logout and clean session if matching user logged in")
Response logoutUser(
@ApiParam(value = "User to log out", required = true)
@PathParam("user") final String user);
diff --git a/rest/rest-resources/pom.xml b/rest/rest-resources/pom.xml
index 17effa1d96..5e605ffb64 100644
--- a/rest/rest-resources/pom.xml
+++ b/rest/rest-resources/pom.xml
@@ -76,10 +76,6 @@
<version>${project.version}</version>
</dependency>
- <dependency>
- <groupId>javax.ws.rs</groupId>
- <artifactId>javax.ws.rs-api</artifactId>
- </dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
@@ -121,6 +117,11 @@
<artifactId>cxf-rt-rs-security-cors</artifactId>
<version>${cxf.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+ <version>${cxf.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.brooklyn</groupId>
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
index 79ca0bfc7a..c59df5394b 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
@@ -48,6 +48,11 @@
ExplicitUsersSecurityProvider.class.getCanonicalName());
public final static ConfigKey<SecurityProvider> SECURITY_PROVIDER_INSTANCE = ConfigKeys.newConfigKey(SecurityProvider.class,
SECURITY_PROVIDER_CLASSNAME.getName()+".internal.instance", "instance of a pre-configured security provider");
+ // TODO document
+ public final static ConfigKey<String> SECURITY_PROVIDER_BUNDLE = ConfigKeys.newStringConfigKey(
+ SECURITY_PROVIDER_CLASSNAME.getName()+".bundle.symbolicName", "Symbolic-name of the bundle containing the Brooklyn SecurityProvider");
+ public final static ConfigKey<String> SECURITY_PROVIDER_BUNDLE_VERSION = ConfigKeys.newStringConfigKey(
+ SECURITY_PROVIDER_CLASSNAME.getName()+".bundle.version", "Version of the bundle containing the Brooklyn SecurityProvider (optional)");
/**
* Explicitly set the users/passwords, e.g. in brooklyn.properties:
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterHelper.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterHelper.java
new file mode 100644
index 0000000000..53cb34ac90
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterHelper.java
@@ -0,0 +1,175 @@
+/*
+ * 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.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.rest.BrooklynWebConfig;
+import org.apache.brooklyn.rest.security.provider.DelegatingSecurityProvider;
+import org.apache.brooklyn.rest.security.provider.SecurityProvider;
+import org.apache.brooklyn.rest.security.provider.SecurityProvider.SecurityProviderDeniedAuthentication;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.text.StringEscapes;
+import org.apache.commons.codec.binary.Base64;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides a filter that performs authentication with the {@link SecurityProvider}
+ * as configured according to {@link BrooklynWebConfig#SECURITY_PROVIDER_CLASSNAME}.
+ *
+ * This replaces the JAAS "BrooklynLoginModule" because that login module requires
+ * Basic auth, which is not flexible enough to support redirect-based solutions like Oauth.
+ *
+ * Unfortunately we seem to need two filters, the Jersey filter for the REST bundle,
+ * and the Javax filter for the static content bundles (in brooklyn-ui/ui-modules).
+ * (We could set up our own Jersey servlet or blueprint for the static content bundles
+ * to re-use the Jersey filter, but that seems like overkill; and surely there's an easy
+ * way to set the Javax filter to run for the REST bundle inside blueprint.xml, but a
+ * few early attempts didn't succeed and the approach of having two filters seems easiest
+ * (especially as they share code for the significant parts, in this class).
+ *
+ * This does give us the opportunity to differentiate the redirect, so that
+ * jersey (REST) requests don't redirect to the auth site, as the redirect requires human intervention.
+ */
+public class BrooklynSecurityProviderFilterHelper {
+
+ public interface Responder {
+ void error(String message, boolean requiresBasicAuth) throws SecurityProviderDeniedAuthentication;
+ }
+
+ /**
+ * 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";
+
+ public static Set<SessionHandler> SESSION_MANAGER_CACHE = MutableSet.of();
+
+ private static final Logger log = LoggerFactory.getLogger(BrooklynSecurityProviderFilterHelper.class);
+
+ // TODO this should be parametrisable
+ public static final String BASIC_REALM_NAME = "brooklyn";
+
+ public static final String BASIC_REALM_HEADER_VALUE = "BASIC realm="+StringEscapes.JavaStringEscapes.wrapJavaString(BASIC_REALM_NAME);
+
+ /* check all contexts for sessions; surprisingly hard to configure session management for karaf/pax web container.
+ * they _really_ want each servlet to have their own sessions. how you're meant to do oauth for multiple servlets i don't know! */
+ public HttpSession getSession(HttpServletRequest webRequest, ManagementContext mgmt, boolean create) {
+ String requestedSessionId = webRequest.getRequestedSessionId();
+
+ log.trace("SESSION for {}, wants session {}", webRequest.getRequestURI(), requestedSessionId);
+
+ if (webRequest instanceof Request) {
+ SessionHandler sm = ((Request)webRequest).getSessionHandler();
+ boolean added = SESSION_MANAGER_CACHE.add( sm );
+ log.trace("SESSION MANAGER found for {}: {} (added={})", webRequest.getRequestURI(), sm, added);
+ } else {
+ log.trace("SESSION MANAGER NOT found for {}: {}", webRequest.getRequestURI(), webRequest);
+ }
+
+ if (requestedSessionId!=null) {
+ for (SessionHandler m: SESSION_MANAGER_CACHE) {
+ HttpSession s = m.getHttpSession(requestedSessionId);
+ if (s!=null) {
+ log.trace("SESSION found for {}: {} (valid={})", webRequest.getRequestURI(), s, m.isValid(s));
+ return s;
+ }
+ }
+ }
+
+ if (create) {
+ HttpSession session = webRequest.getSession(true);
+ log.trace("SESSION creating for {}: {}", webRequest.getRequestURI(), session);
+ return session;
+ }
+
+ return null; // not found
+ }
+
+ public void run(HttpServletRequest webRequest, ManagementContext mgmt) throws SecurityProviderDeniedAuthentication {
+ SecurityProvider provider = getProvider(mgmt);
+ HttpSession session = getSession(webRequest, mgmt, false);
+
+ if (provider.isAuthenticated(session)) {
+ return;
+ }
+
+ String user = null, pass = null;
+ if (provider.requiresUserPass()) {
+ String authorization = webRequest.getHeader("Authorization");
+ if (authorization != null) {
+ String userpass = new String(Base64.decodeBase64(authorization.substring(6)));
+ int idxColon = userpass.indexOf(":");
+ if (idxColon >= 0) {
+ user = userpass.substring(0, idxColon);
+ pass = userpass.substring(idxColon + 1);
+ } else {
+ throw abort("Invalid authorization string", provider.requiresUserPass());
+ }
+ } else {
+ throw abort("Authorization required", provider.requiresUserPass());
+ }
+ }
+
+ if (session==null) {
+ // only create the session if an auth string is supplied
+ session = getSession(webRequest, mgmt, true);
+ }
+ session.setAttribute(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE, webRequest.getRemoteAddr());
+
+ if (provider.authenticate(session, user, pass)) {
+ if (user != null) {
+ session.setAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE, user);
+ }
+ return;
+ }
+
+ throw abort("Authentication failed", provider.requiresUserPass());
+ }
+
+ private SecurityProviderDeniedAuthentication abort(String msg, boolean requiresUserPass) throws SecurityProviderDeniedAuthentication {
+ ResponseBuilder response = Response.status(Status.UNAUTHORIZED);
+ if (requiresUserPass) {
+ response.header(HttpHeader.WWW_AUTHENTICATE.asString(), BASIC_REALM_HEADER_VALUE);
+ }
+ response.header(HttpHeader.CONTENT_TYPE.asString(), MediaType.TEXT_PLAIN);
+ response.entity(msg);
+ throw new SecurityProviderDeniedAuthentication(response.build());
+ }
+
+ protected SecurityProvider getProvider(ManagementContext mgmt) {
+ // we don't cache here (could, it might be faster) but the delegate does use a cache
+ return new DelegatingSecurityProvider(mgmt);
+ }
+
+}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJavax.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJavax.java
new file mode 100644
index 0000000000..eb26d8a621
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJavax.java
@@ -0,0 +1,91 @@
+/*
+ * 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 javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.rest.security.provider.SecurityProvider.SecurityProviderDeniedAuthentication;
+import org.apache.brooklyn.rest.util.ManagementContextProvider;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+
+/** See {@link BrooklynSecurityProviderFilterHelper} */
+public class BrooklynSecurityProviderFilterJavax implements Filter {
+
+ private static final Logger log = LoggerFactory.getLogger(BrooklynSecurityProviderFilterJavax.class);
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ // no init needed
+ log.trace("BrooklynSecurityProviderFilterJavax.init");
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+
+ try {
+ log.trace("BrooklynSecurityProviderFilterJavax.doFilter {}", request);
+ ManagementContext mgmt = new ManagementContextProvider(request.getServletContext()).getManagementContext();
+ Preconditions.checkNotNull(mgmt, "Brooklyn management context not available; cannot authenticate");
+ new BrooklynSecurityProviderFilterHelper().run((HttpServletRequest)request, mgmt);
+
+ chain.doFilter(request, response);
+
+ } catch (SecurityProviderDeniedAuthentication e) {
+ log.trace("BrooklynSecurityProviderFilterJavax.doFilter caught SecurityProviderDeniedAuthentication", e);
+ HttpServletResponse rout = ((HttpServletResponse)response);
+ Response rin = e.getResponse();
+ if (rin==null) rin = Response.status(Status.UNAUTHORIZED).build();
+
+ rout.setStatus(rin.getStatus());
+
+ // note content-type is explicitly set in some Response objects, but this should set it
+ rin.getHeaders().forEach((k,v) -> v.forEach(v2 -> rout.addHeader(k, Strings.toString(v2))));
+
+ Object body = rin.getEntity();
+ if (body!=null) {
+ response.getWriter().write(Strings.toString(body));
+ response.getWriter().flush();
+ }
+ }
+ }
+
+ @Override
+ public void destroy() {
+ // no clean-up needed
+ log.trace("BrooklynSecurityProviderFilterJavax.destroy");
+ }
+
+}
\ No newline at end of file
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJersey.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJersey.java
new file mode 100644
index 0000000000..d5d7cd2f8f
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJersey.java
@@ -0,0 +1,79 @@
+/*
+ * 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.annotation.Priority;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.ext.ContextResolver;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.rest.security.provider.SecurityProvider.SecurityProviderDeniedAuthentication;
+import org.eclipse.jetty.http.HttpHeader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** See {@link BrooklynSecurityProviderFilterHelper} */
+@Provider
+@Priority(100)
+public class BrooklynSecurityProviderFilterJersey implements ContainerRequestFilter {
+
+ private static final Logger log = LoggerFactory.getLogger(BrooklynSecurityProviderFilterJersey.class);
+
+ @Context
+ HttpServletRequest webRequest;
+
+ @Context
+ private ContextResolver<ManagementContext> mgmtC;
+
+ @SuppressWarnings("resource")
+ @Override
+ public void filter(ContainerRequestContext requestContext) throws IOException {
+ log.trace("BrooklynSecurityProviderFilterJersey.filter {}", requestContext);
+ try {
+ new BrooklynSecurityProviderFilterHelper().run(webRequest, mgmtC.getContext(ManagementContext.class));
+ } catch (SecurityProviderDeniedAuthentication e) {
+ Response rin = e.getResponse();
+ if (rin==null) rin = Response.status(Status.UNAUTHORIZED).build();
+
+ if (rin.getStatus()==Status.FOUND.getStatusCode()) {
+ String location = rin.getHeaderString(HttpHeader.LOCATION.asString());
+ if (location!=null) {
+ log.trace("Redirect to {} for authentication",location);
+ final UriBuilder uriBuilder = UriBuilder.fromPath(location);
+ rin = Response.temporaryRedirect(uriBuilder.build()).entity("Authentication is required at "+location).build();
+ } else {
+ log.trace("Unauthorized");
+ rin = Response.status(Status.UNAUTHORIZED).entity("Authentication is required").build();
+ }
+ }
+ requestContext.abortWith(rin);
+ }
+ }
+
+}
+
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/CorsImplSupplierFilter.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/CorsImplSupplierFilter.java
index 51c9e5c2ae..9262c00ac6 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/CorsImplSupplierFilter.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/CorsImplSupplierFilter.java
@@ -59,6 +59,7 @@
* Apache Brooklyn API calls do not use CORS annotations so findResourceMethod is set to false.
*/
@Provider
+@SuppressWarnings("serial")
public class CorsImplSupplierFilter extends CrossOriginResourceSharingFilter {
/**
* @see CrossOriginResourceSharingFilter#setAllowOrigins(List<String>)
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/EntitlementContextFilter.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/EntitlementContextFilter.java
index a039b57f0a..d2ce05595a 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/EntitlementContextFilter.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/EntitlementContextFilter.java
@@ -23,6 +23,7 @@
import javax.annotation.Priority;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
@@ -31,8 +32,10 @@
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext;
import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
import org.apache.brooklyn.core.mgmt.entitlement.WebEntitlementContext;
+import org.apache.brooklyn.util.text.Strings;
@Provider
@Priority(400)
@@ -42,16 +45,37 @@
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
+ String userName = null;
+
+ // first see if there is a principal
SecurityContext securityContext = requestContext.getSecurityContext();
Principal user = securityContext.getUserPrincipal();
+ if (user!=null) {
+ userName = user.getName();
+ } else {
+
+ // now look in session attribute - because principals hard to set from javax filter
+ if (request!=null) {
+ HttpSession s = request.getSession(false);
+ if (s!=null) {
+ userName = Strings.toString(s.getAttribute(
+ BrooklynSecurityProviderFilterHelper.AUTHENTICATED_USER_SESSION_ATTRIBUTE));
+ }
+ }
+ }
+
+ if (userName != null) {
+ EntitlementContext oldEntitlement = Entitlements.getEntitlementContext();
+ if (oldEntitlement!=null && !userName.equals(oldEntitlement.user())) {
+ throw new IllegalStateException("Illegal entitement context switch, from user "+oldEntitlement.user()+" to "+userName);
+ }
+
+ String uri = request.getRequestURI();
+ String remoteAddr = request.getRemoteAddr();
- if (user != null) {
- String uri = request.getRequestURI();
- String remoteAddr = request.getRemoteAddr();
-
- String uid = RequestTaggingRsFilter.getTag();
- WebEntitlementContext entitlementContext = new WebEntitlementContext(user.getName(), remoteAddr, uri, uid);
- Entitlements.setEntitlementContext(entitlementContext);
+ String uid = RequestTaggingRsFilter.getTag();
+ WebEntitlementContext entitlementContext = new WebEntitlementContext(userName, remoteAddr, uri, uid);
+ Entitlements.setEntitlementContext(entitlementContext);
}
}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
index 28eac1ca0f..128d06a28f 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
@@ -33,7 +33,6 @@
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
-import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.text.Strings;
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LogoutResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LogoutResource.java
index e3329d2f8a..a03cb24abf 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LogoutResource.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LogoutResource.java
@@ -30,15 +30,11 @@
import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
import org.apache.brooklyn.core.mgmt.entitlement.WebEntitlementContext;
import org.apache.brooklyn.rest.api.LogoutApi;
-import org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule;
+import org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilterHelper;
import org.apache.brooklyn.util.exceptions.Exceptions;
-import com.google.common.net.HttpHeaders;
-
public class LogoutResource extends AbstractBrooklynRestResource implements LogoutApi {
- private static final String BASIC_REALM_WEBCONSOLE = "Basic realm=\""+BrooklynLoginModule.DEFAULT_ROLE+"\"";
-
@Context HttpServletRequest req;
@Context UriInfo uri;
@@ -49,7 +45,6 @@ public Response logout() {
if (ctx==null) {
return Response.status(Status.BAD_REQUEST)
.entity("No user logged in")
- .header(HttpHeaders.WWW_AUTHENTICATE, BASIC_REALM_WEBCONSOLE)
.build();
}
@@ -64,21 +59,21 @@ public Response logout() {
@Override
public Response unAuthorize() {
return Response.status(Status.UNAUTHORIZED)
- .header(HttpHeaders.WWW_AUTHENTICATE, BASIC_REALM_WEBCONSOLE)
- .build();
+ // NB: 2019-01 no longer returns a realm (there might not be a realm; in this code we don't know)
+ // method is now deprecated anyway
+ .build();
}
@Override
public Response logoutUser(String user) {
- // Will work when switching users, but will keep re-authenticating if user types in same user name.
- // Could improve by keeping state in cookies to decide whether to request auth or declare successfull re-auth.
WebEntitlementContext ctx = (WebEntitlementContext) Entitlements.getEntitlementContext();
if (user.equals(ctx.user())) {
doLogout();
- return Response.status(Status.UNAUTHORIZED)
- .header(HttpHeaders.WWW_AUTHENTICATE, BASIC_REALM_WEBCONSOLE)
- .build();
+ return Response.status(Status.OK)
+ // 2019-01 no longer returns unauthorized, returns OK to indicate user is successfully logged out
+ // also the realm is removed (there might not be a realm; in this code we don't know)
+ .build();
} else {
return Response.temporaryRedirect(uri.getAbsolutePathBuilder().replacePath("/").build()).build();
}
@@ -86,6 +81,7 @@ public Response logoutUser(String user) {
private void doLogout() {
try {
+ req.getSession().removeAttribute(BrooklynSecurityProviderFilterHelper.AUTHENTICATED_USER_SESSION_ATTRIBUTE);
req.logout();
} catch (ServletException e) {
Exceptions.propagate(e);
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/BrooklynLoginModule.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/BrooklynLoginModule.java
deleted file mode 100644
index 32684bdacb..0000000000
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/BrooklynLoginModule.java
+++ /dev/null
@@ -1,366 +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 org.apache.brooklyn.rest.security.jaas;
-
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.config.StringConfigMap;
-import org.apache.brooklyn.rest.BrooklynWebConfig;
-import org.apache.brooklyn.rest.security.provider.DelegatingSecurityProvider;
-import org.apache.brooklyn.rest.security.provider.SecurityProvider;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.text.Strings;
-import org.eclipse.jetty.server.HttpChannel;
-import org.eclipse.jetty.server.HttpConnection;
-import org.eclipse.jetty.server.Request;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.FailedLoginException;
-import javax.security.auth.login.LoginException;
-import javax.security.auth.spi.LoginModule;
-import javax.servlet.http.HttpSession;
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Map;
-import java.util.Optional;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-// http://docs.oracle.com/javase/7/docs/technotes/guides/security/jaas/JAASLMDevGuide.html
-
-/**
- * <p>
- * JAAS module delegating authentication to the {@link SecurityProvider} implementation
- * configured in {@literal brooklyn.properties}, key {@literal brooklyn.webconsole.security.provider}.
- * <p>
- * <p>
- * If used in an OSGi environment only implementations visible from {@literal brooklyn-rest-server} are usable by default.
- * To use a custom security provider add the following configuration to the its bundle in {@literal src/main/resources/OSGI-INF/bundle/security-provider.xml}:
- * <p>
- * <pre>
- * {@code
- * <?xml version="1.0" encoding="UTF-8"?>
- * <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
- * xmlns:jaas="http://karaf.apache.org/xmlns/jaas/v1.1.0"
- * xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0">
- *
- * <jaas:config name="karaf" rank="1">
- * <jaas:module className="org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule"
- * flags="required">
- * brooklyn.webconsole.security.provider.symbolicName=BUNDLE_SYMBOLIC_NAME
- * brooklyn.webconsole.security.provider.version=BUNDLE_VERSION
- * </jaas:module>
- * </jaas:config>
- *
- * </blueprint>
- * }
- * </pre>
- */
-// Needs an explicit "org.apache.karaf.jaas.config" Import-Package in the manifest!
-public class BrooklynLoginModule implements LoginModule {
- private static final Logger log = LoggerFactory.getLogger(BrooklynLoginModule.class);
-
- /**
- * 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";
-
- private static class BasicPrincipal implements Principal {
- private String name;
-
- public BasicPrincipal(String name) {
- this.name = checkNotNull(name, "name");
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public int hashCode() {
- return name.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof BasicPrincipal) {
- return name.equals(((BasicPrincipal) obj).name);
- }
- return false;
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + "[" + name + "]";
- }
- }
-
- public static class UserPrincipal extends BasicPrincipal {
- public UserPrincipal(String name) {
- super(name);
- }
- }
-
- public static class RolePrincipal extends BasicPrincipal {
- public RolePrincipal(String name) {
- super(name);
- }
- }
-
- public static final String PROPERTY_BUNDLE_SYMBOLIC_NAME = BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME.getName() + ".symbolicName";
- public static final String PROPERTY_BUNDLE_VERSION = BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME.getName() + ".version";
- /**
- * SecurityProvider doesn't know about roles, just attach one by default. Use the one specified here or DEFAULT_ROLE
- */
- public static final String PROPERTY_ROLE = BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME.getName() + ".role";
- public static final String DEFAULT_ROLE = "webconsole";
-
- private Map<String, ?> options;
- private BundleContext bundleContext;
-
- private HttpSession providerSession;
-
- private SecurityProvider provider;
- private Subject subject;
- private CallbackHandler callbackHandler;
- private boolean loginSuccess;
- private boolean commitSuccess;
- private Collection<Principal> principals;
-
- public BrooklynLoginModule() {
- }
-
- private synchronized static SecurityProvider createDefaultSecurityProvider(ManagementContext mgmt) {
- return new DelegatingSecurityProvider(mgmt);
- }
-
- private ManagementContext getManagementContext() {
- return ManagementContextHolder.getManagementContext();
- }
-
- @Override
- public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
- this.subject = subject;
- this.callbackHandler = callbackHandler;
- this.options = options;
-
- this.bundleContext = (BundleContext) options.get(BundleContext.class.getName());
-
- loginSuccess = false;
- commitSuccess = false;
-
- initProvider();
- }
-
- private void initProvider() {
- StringConfigMap brooklynProperties = getManagementContext().getConfig();
- provider = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE);
- String symbolicName = (String) options.get(PROPERTY_BUNDLE_SYMBOLIC_NAME);
- String version = (String) options.get(PROPERTY_BUNDLE_VERSION);
- String className = (String) options.get(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME.getName());
- if (className != null && symbolicName == null) {
- throw new IllegalStateException("Missing JAAS module property " + PROPERTY_BUNDLE_SYMBOLIC_NAME + " pointing at the bundle where to load the security provider from.");
- }
- if (provider != null) return;
- provider = getManagementContext().getScratchpad().get(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE);
- if (provider != null) return;
- if (symbolicName != null) {
- if (className == null) {
- className = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME);
- }
- if (className != null) {
- try {
- Collection<Bundle> bundles = getMatchingBundles(symbolicName, version);
- if (bundles.isEmpty()) {
- throw new IllegalStateException("No bundle " + symbolicName + ":" + version + " found");
- } else if (bundles.size() > 1) {
- log.warn("Found multiple bundles matching symbolicName " + symbolicName + " and version " + version +
- " while trying to load security provider " + className + ". Will use first one that loads the class successfully.");
- }
- provider = tryLoadClass(className, bundles);
- if (provider == null) {
- throw new ClassNotFoundException("Unable to load class " + className + " from bundle " + symbolicName + ":" + version);
- }
- } catch (Exception e) {
- Exceptions.propagateIfFatal(e);
- throw new IllegalStateException("Can not load or create security provider " + className + " for bundle " + symbolicName + ":" + version, e);
- }
- }
- } else {
- log.debug("Delegating security provider loading to Brooklyn.");
- provider = createDefaultSecurityProvider(getManagementContext());
- }
-
- log.debug("Using security provider " + provider);
- }
-
- private SecurityProvider tryLoadClass(String className, Collection<Bundle> bundles)
- throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
- for (Bundle b : bundles) {
- try {
- @SuppressWarnings("unchecked")
- Class<? extends SecurityProvider> securityProviderType = (Class<? extends SecurityProvider>) b.loadClass(className);
- return DelegatingSecurityProvider.createSecurityProviderInstance(getManagementContext(), securityProviderType);
- } catch (ClassNotFoundException e) {
- }
- }
- return null;
- }
-
- private Collection<Bundle> getMatchingBundles(final String symbolicName, final String version) {
- Collection<Bundle> bundles = new ArrayList<>();
- for (Bundle b : bundleContext.getBundles()) {
- if (b.getSymbolicName().equals(symbolicName) &&
- (version == null || b.getVersion().toString().equals(version))) {
- bundles.add(b);
- }
- }
- return bundles;
- }
-
- @Override
- public boolean login() throws LoginException {
- if (callbackHandler == null) {
- loginSuccess = false;
- throw new FailedLoginException("Username and password not available");
- }
-
- NameCallback cbName = new NameCallback("Username: ");
- PasswordCallback cbPassword = new PasswordCallback("Password: ", false);
-
- Callback[] callbacks = {cbName, cbPassword};
-
- try {
- callbackHandler.handle(callbacks);
- } catch (IOException ioe) {
- throw new LoginException(ioe.getMessage());
- } catch (UnsupportedCallbackException uce) {
- throw new LoginException(uce.getMessage() + " not available to obtain information from user");
- }
- String user = cbName.getName();
- String password = new String(cbPassword.getPassword());
-
- providerSession = new SecurityProviderHttpSession();
-
- Request req = getJettyRequest();
- if (req != null) {
- String remoteAddr = req.getRemoteAddr();
- providerSession.setAttribute(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE, remoteAddr);
- }
-
- if (!provider.authenticate(providerSession, user, password)) {
- loginSuccess = false;
- throw new FailedLoginException("Incorrect username or password");
- }
-
- if (user != null) {
- providerSession.setAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE, user);
- }
-
- principals = new ArrayList<>(2);
- principals.add(new UserPrincipal(user));
- // Could introduce a new interface SecurityRoleAware, implemented by
- // the SecurityProviders, returning the roles a user has assigned.
- // For now a static role is good enough.
- String role = (String) options.get(PROPERTY_ROLE);
- if (role == null) {
- role = DEFAULT_ROLE;
- }
- if (Strings.isNonEmpty(role)) {
- principals.add(new RolePrincipal(role));
- }
- loginSuccess = true;
- return true;
- }
-
- @Override
- public boolean commit() throws LoginException {
- if (loginSuccess) {
- if (subject.isReadOnly()) {
- throw new LoginException("Can't commit read-only subject");
- }
- subject.getPrincipals().addAll(principals);
- }
-
- commitSuccess = true;
- return loginSuccess;
- }
-
- @Override
- public boolean abort() throws LoginException {
- if (loginSuccess && commitSuccess) {
- removePrincipal();
- }
- clear();
- return loginSuccess;
- }
-
- @Override
- public boolean logout() throws LoginException {
- Request req = getJettyRequest();
- if (req != null) {
- log.info("REST logging {} out",
- providerSession.getAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE));
- provider.logout(req.getSession());
- req.getSession().removeAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE);
- } else {
- log.error("Request object not available for logout");
- }
-
- removePrincipal();
- clear();
- return true;
- }
-
- private void removePrincipal() throws LoginException {
- if (subject.isReadOnly()) {
- throw new LoginException("Read-only subject");
- }
- subject.getPrincipals().removeAll(principals);
- }
-
- private void clear() {
- subject = null;
- callbackHandler = null;
- principals = null;
- }
-
- private Request getJettyRequest() {
- return Optional.ofNullable(HttpConnection.getCurrentConnection())
- .map(HttpConnection::getHttpChannel)
- .map(HttpChannel::getRequest)
- .orElse(null);
- }
-
-}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/JaasUtils.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/JaasUtils.java
deleted file mode 100644
index 94aba5de96..0000000000
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/JaasUtils.java
+++ /dev/null
@@ -1,48 +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 org.apache.brooklyn.rest.security.jaas;
-
-import java.net.URL;
-
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class JaasUtils {
- private static final Logger log = LoggerFactory.getLogger(JaasUtils.class);
-
- private static final String JAAS_CONFIG = "java.security.auth.login.config";
-
- public static void init(ManagementContext mgmt) {
- ManagementContextHolder.setManagementContextStatic(mgmt);
- String config = System.getProperty(JAAS_CONFIG);
- if (config == null) {
- URL configUrl = JaasUtils.class.getResource("/jaas.conf");
- if (configUrl != null) {
- log.debug("Using classpath JAAS config from " + configUrl.toExternalForm());
- System.setProperty(JAAS_CONFIG, configUrl.toExternalForm());
- } else {
- log.error("Can't find " + JAAS_CONFIG + " on classpath. Web server authentication will fail.");
- }
- } else {
- log.debug("Using externally configured JAAS at " + config);
- }
- }
-
-}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/ManagementContextHolder.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/ManagementContextHolder.java
deleted file mode 100644
index 84704f1547..0000000000
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/ManagementContextHolder.java
+++ /dev/null
@@ -1,36 +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 org.apache.brooklyn.rest.security.jaas;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-
-public class ManagementContextHolder {
- private static ManagementContext mgmt;
- public static ManagementContext getManagementContext() {
- return checkNotNull(mgmt, "Management context not set yet");
- }
- public void setManagementContext(ManagementContext mgmt) {
- setManagementContextStatic(mgmt);
- }
- public static void setManagementContextStatic(ManagementContext mgmt) {
- ManagementContextHolder.mgmt = checkNotNull(mgmt, "mgmt");
- }
-}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/SecurityProviderHttpSession.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/SecurityProviderHttpSession.java
deleted file mode 100644
index 98f1e6d950..0000000000
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/SecurityProviderHttpSession.java
+++ /dev/null
@@ -1,120 +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 org.apache.brooklyn.rest.security.jaas;
-
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import javax.servlet.ServletContext;
-import javax.servlet.http.HttpSession;
-
-import org.apache.brooklyn.util.text.Identifiers;
-
-/** mock session, used only for performing authentication */
-public class SecurityProviderHttpSession implements HttpSession {
- String id = Identifiers.makeRandomId(5);
- Map<String, Object> attributes = new ConcurrentHashMap<>();
-
- @Override
- public long getCreationTime() {
- return 0;
- }
-
- @Override
- public String getId() {
- return id;
- }
-
- @Override
- public long getLastAccessedTime() {
- return 0;
- }
-
- @Override
- public ServletContext getServletContext() {
- return null;
- }
-
- @Override
- public void setMaxInactiveInterval(int interval) {
- }
-
- @Override
- public int getMaxInactiveInterval() {
- return 0;
- }
-
- @Override
- @Deprecated //in interface
- public javax.servlet.http.HttpSessionContext getSessionContext() {
- return null;
- }
-
- @Override
- public Object getAttribute(String name) {
- return attributes.get(name);
- }
-
- @Override
- public Object getValue(String name) {
- return null;
- }
-
- @Override
- public Enumeration<String> getAttributeNames() {
- return Collections.enumeration(attributes.keySet());
- }
-
- @Override
- public String[] getValueNames() {
- return null;
- }
-
- @Override
- public void setAttribute(String name, Object value) {
- attributes.put(name, value);
- }
-
- @Override
- public void putValue(String name, Object value) {
- }
-
- @Override
- public void removeAttribute(String name) {
- attributes.remove(name);
- }
-
- @Override
- public void removeValue(String name) {
- }
-
- @Override
- public void invalidate() {
- id = Identifiers.makeRandomId(5);
- attributes.clear();
- }
-
- @Override
- public boolean isNew() {
- return false;
- }
-
-}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
index 97b4fe1e97..b15ef3a75e 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
@@ -20,7 +20,8 @@
import javax.servlet.http.HttpSession;
-/** provider who allows everyone */
+/** provider who allows all access with no need to supply a user
+ * (see also test case AuthenticateAnyoneSecurityProvider) */
public class AnyoneSecurityProvider implements SecurityProvider {
@Override
@@ -28,8 +29,15 @@ public boolean isAuthenticated(HttpSession session) {
return true;
}
+ @Override
+ public boolean requiresUserPass() {
+ // doesn't matter as isAuth always returns true, this should never be called
+ return false;
+ }
+
@Override
public boolean authenticate(HttpSession session, String user, String password) {
+ // doesn't matter as isAuth always returns true, this should never be called
return true;
}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
index a9769750fa..5418924046 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
@@ -20,7 +20,7 @@
import javax.servlet.http.HttpSession;
-/** provider who disallows everyone */
+/** provider who disallows everyone, though it does require a user/pass */
public class BlackholeSecurityProvider implements SecurityProvider {
@Override
@@ -37,4 +37,10 @@ public boolean authenticate(HttpSession session, String user, String password) {
public boolean logout(HttpSession session) {
return true;
}
+
+ @Override
+ public boolean requiresUserPass() {
+ return true;
+ }
+
}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
index 7b8e4a5ec6..3400d0a05e 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
@@ -70,4 +70,9 @@ private boolean isRemoteAddressLocalhost(HttpSession session) {
public String toString() {
return JavaClassNames.cleanSimpleClassName(this);
}
+
+ @Override
+ public boolean requiresUserPass() {
+ return true;
+ }
}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
index 94b7894553..c3c74500ad 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
@@ -20,6 +20,8 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.http.HttpSession;
@@ -27,9 +29,12 @@
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.config.StringConfigMap;
import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.rest.BrooklynWebConfig;
import org.apache.brooklyn.util.core.ClassLoaderUtils;
-import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -72,37 +77,33 @@ private synchronized SecurityProvider loadDelegate() {
SecurityProvider presetDelegate = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE);
if (presetDelegate!=null) {
- log.info("REST using pre-set security provider " + presetDelegate);
+ log.trace("Brooklyn security: using pre-set security provider {}", presetDelegate);
return presetDelegate;
}
String className = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME);
if (delegate != null && BrooklynWebConfig.hasNoSecurityOptions(mgmt.getConfig())) {
- log.debug("{} refusing to change from {}: No security provider set in reloaded properties.",
+ log.debug("Brooklyn security: {} refusing to change from {}: No security provider set in reloaded properties.",
this, delegate);
return delegate;
}
- log.info("REST using security provider " + className);
try {
- ClassLoaderUtils clu = new ClassLoaderUtils(this, mgmt);
- Class<? extends SecurityProvider> clazz;
- try {
- clazz = (Class<? extends SecurityProvider>) clu.loadClass(className);
- } catch (Exception e) {
- String oldPackage = "brooklyn.web.console.security.";
- if (className.startsWith(oldPackage)) {
- className = Strings.removeFromStart(className, oldPackage);
- className = DelegatingSecurityProvider.class.getPackage().getName() + "." + className;
- clazz = (Class<? extends SecurityProvider>) clu.loadClass(className);
- log.warn("Deprecated package " + oldPackage + " detected; please update security provider to point to " + className);
- } else throw e;
+ String bundle = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_BUNDLE);
+ if (bundle!=null) {
+ String bundleVersion = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_BUNDLE_VERSION);
+ log.info("Brooklyn security: using security provider " + className + " from " + bundle+":"+bundleVersion);
+ BundleContext bundleContext = ((ManagementContextInternal)mgmt).getOsgiManager().get().getFramework().getBundleContext();
+ delegate = loadProviderFromBundle(mgmt, bundleContext, bundle, bundleVersion, className);
+ } else {
+ log.info("Brooklyn security: using security provider " + className);
+ ClassLoaderUtils clu = new ClassLoaderUtils(this, mgmt);
+ Class<? extends SecurityProvider> clazz = (Class<? extends SecurityProvider>) clu.loadClass(className);
+ delegate = createSecurityProviderInstance(mgmt, clazz);
}
-
- delegate = createSecurityProviderInstance(mgmt, clazz);
} catch (Exception e) {
- log.warn("REST unable to instantiate security provider " + className + "; all logins are being disallowed", e);
+ log.warn("Brooklyn security: unable to instantiate security provider " + className + "; all logins are being disallowed", e);
delegate = new BlackholeSecurityProvider();
}
@@ -113,6 +114,52 @@ private synchronized SecurityProvider loadDelegate() {
return delegate;
}
+ public static SecurityProvider loadProviderFromBundle(
+ ManagementContext mgmt, BundleContext bundleContext,
+ String symbolicName, String version, String className) {
+ try {
+ Collection<Bundle> bundles = getMatchingBundles(bundleContext, symbolicName, version);
+ if (bundles.isEmpty()) {
+ throw new IllegalStateException("No bundle " + symbolicName + ":" + version + " found");
+ } else if (bundles.size() > 1) {
+ log.warn("Brooklyn security: found multiple bundles matching symbolicName " + symbolicName + " and version " + version +
+ " while trying to load security provider " + className + ". Will use first one that loads the class successfully.");
+ }
+ SecurityProvider p = tryLoadClass(mgmt, className, bundles);
+ if (p == null) {
+ throw new ClassNotFoundException("Unable to load class " + className + " from bundle " + symbolicName + ":" + version);
+ }
+ return p;
+ } catch (Exception e) {
+ Exceptions.propagateIfFatal(e);
+ throw new IllegalStateException("Can not load or create security provider " + className + " for bundle " + symbolicName + ":" + version, e);
+ }
+ }
+
+ private static SecurityProvider tryLoadClass(ManagementContext mgmt, String className, Collection<Bundle> bundles)
+ throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
+ for (Bundle b : bundles) {
+ try {
+ @SuppressWarnings("unchecked")
+ Class<? extends SecurityProvider> securityProviderType = (Class<? extends SecurityProvider>) b.loadClass(className);
+ return DelegatingSecurityProvider.createSecurityProviderInstance(mgmt, securityProviderType);
+ } catch (ClassNotFoundException e) {
+ }
+ }
+ return null;
+ }
+
+ private static Collection<Bundle> getMatchingBundles(BundleContext bundleContext, final String symbolicName, final String version) {
+ Collection<Bundle> bundles = new ArrayList<>();
+ for (Bundle b : bundleContext.getBundles()) {
+ if (b.getSymbolicName().equals(symbolicName) &&
+ (version == null || b.getVersion().toString().equals(version))) {
+ bundles.add(b);
+ }
+ }
+ return bundles;
+ }
+
public static SecurityProvider createSecurityProviderInstance(ManagementContext mgmt,
Class<? extends SecurityProvider> clazz) throws NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException {
@@ -140,19 +187,12 @@ protected void invalidateExistingSessions() {
@Override
public boolean isAuthenticated(HttpSession session) {
- if (session == null) return false;
- Object modCountWhenFirstAuthenticated = session.getAttribute(getModificationCountKey());
- boolean authenticated = getDelegate().isAuthenticated(session) &&
- Long.valueOf(modCount.get()).equals(modCountWhenFirstAuthenticated);
- return authenticated;
+ return getDelegate().isAuthenticated(session);
}
@Override
- public boolean authenticate(HttpSession session, String user, String password) {
+ public boolean authenticate(HttpSession session, String user, String password) throws SecurityProviderDeniedAuthentication {
boolean authenticated = getDelegate().authenticate(session, user, password);
- if (authenticated) {
- session.setAttribute(getModificationCountKey(), modCount.get());
- }
if (log.isTraceEnabled() && authenticated) {
log.trace("User {} authenticated with provider {}", user, getDelegate());
} else if (!authenticated && log.isDebugEnabled()) {
@@ -163,14 +203,17 @@ public boolean authenticate(HttpSession session, String user, String password) {
@Override
public boolean logout(HttpSession session) {
- boolean logout = getDelegate().logout(session);
- if (logout) {
- session.removeAttribute(getModificationCountKey());
- }
- return logout;
+ return getDelegate().logout(session);
+ }
+
+ @Override
+ public boolean requiresUserPass() {
+ return getDelegate().requiresUserPass();
}
- private String getModificationCountKey() {
- return getClass().getName() + ".ModCount";
+ @Override
+ public String toString() {
+ return super.toString()+"["+getDelegate()+"]";
}
+
}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
index a0795cb9c2..2d9be552a3 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
@@ -114,4 +114,9 @@ public static boolean checkPassword(String candidatePassword, String expectedPas
return false;
}
+
+ @Override
+ public boolean requiresUserPass() {
+ return true;
+ }
}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
index d3636e953c..cc9b013bd5 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
@@ -129,4 +129,9 @@ public synchronized static void checkCanLoad() {
throw Exceptions.propagate(new ClassNotFoundException("Unable to load LDAP classes ("+LDAP_CONTEXT_FACTORY+") required for Brooklyn LDAP security provider"));
}
}
+
+ @Override
+ public boolean requiresUserPass() {
+ return true;
+ }
}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/OauthSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/OauthSecurityProvider.java
new file mode 100644
index 0000000000..70110329da
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/OauthSecurityProvider.java
@@ -0,0 +1,322 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.security.provider;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpSession;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilterHelper;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.yaml.Yamls;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+
+/** Configurable OAuth redirect security provider
+ *
+ * Redirects all inbound requests to an oath web server unless a session token is specified. */
+@Beta // work in progress
+public class OauthSecurityProvider implements SecurityProvider {
+
+ public static final Logger log = LoggerFactory.getLogger(OauthSecurityProvider.class);
+
+ private static final String OAUTH_ACCESS_TOKEN_SESSION_KEY = "org.apache.brooklyn.security.oauth.access_token";
+ private static final String OAUTH_ACCESS_TOKEN_EXPIRY_UTC_KEY = "org.apache.brooklyn.security.oauth.access_token_expiry_utc";
+
+ private static final String OAUTH_AUTH_CODE_PARAMETER_FROM_USER = "code";
+ private static final String OAUTH_AUTH_CODE_PARAMETER_FOR_SERVER = OAUTH_AUTH_CODE_PARAMETER_FROM_USER;
+
+// private static String KEY_PREFIX = BrooklynWebConfig.BASE_NAME_SECURITY+".oauth.";
+// ConfigKey<String> URI_GET_TOKEN_KEY = ConfigKeys.newStringConfigKey(KEY_PREFIX+"uriGetToken", "URL where token can be fetched");
+ // TODO parameterise values below with keys as above
+
+ // tempting to use getJettyRequest().getRequestURL().toString();
+ // but some oauth providers require this to be declared
+ private String callbackUri = "http://localhost.io:8081/";
+ private String accessTokenResponseKey = "access_token";
+ private String audience = "audience";
+ private Duration validity = Duration.hours(1);
+
+ // google test data - hard-coded for now
+ private String uriGetToken = "https://accounts.google.com/o/oauth2/token";
+ private String uriAuthorize = "https://accounts.google.com/o/oauth2/auth";
+ private String uriTokenInfo = "https://www.googleapis.com/oauth2/v1/tokeninfo";
+ private String clientId = "789182012565-burd24h3bc0im74g2qemi7lnihvfqd02.apps.googleusercontent.com";
+ private String clientSecret = "X00v-LfU34U4SfsHqPKMWfQl";
+
+ // github test data
+// private String uriGetToken = "https://github.com/login/oauth/authorize";
+// private String uriAuthorize = "https://github.com/login/oauth/authorize";
+// private String uriTokenInfo = "https://github.com/login/oauth/access_token";
+// private String clientId = "7f76b9970d8ac15b30b0";
+// private String clientSecret = "9e15f8dd651f0b1896a3a582f17fa82f049fc910";
+
+ protected final ManagementContext mgmt;
+
+ public OauthSecurityProvider(ManagementContext mgmt) {
+ this.mgmt = mgmt;
+ initialize();
+ }
+
+ private synchronized void initialize() {
+ // TODO set these keys
+// Preconditions.checkNotNull(mgmt.getConfig().getConfig(URI_GET_TOKEN_KEY), "URI to get token must be set: "+URI_GET_TOKEN_KEY.getName());
+ }
+
+ @Override
+ public boolean isAuthenticated(HttpSession session) {
+ // TODO tidy log messages
+ log.info("isAuthenticated 1 "+getJettyRequest().getRequestURI()+" "+session+" ... "+this);
+ if(session==null) return false;
+
+ Object token = session.getAttribute(OAUTH_ACCESS_TOKEN_SESSION_KEY);
+ // TODO is it valid?
+ return token!=null;
+ }
+
+ @Override
+ public boolean authenticate(HttpSession session, String user, String password) throws SecurityProviderDeniedAuthentication {
+ log.info("authenticate "+session+" "+user);
+
+ if (isAuthenticated(session)) {
+ return true;
+ }
+
+ Request request = getJettyRequest();
+ // Redirection from the authenticator server
+ String code = request.getParameter(OAUTH_AUTH_CODE_PARAMETER_FROM_USER);
+ // Getting token, if exists, from the current session
+ String token = (String) request.getSession().getAttribute(OAUTH_ACCESS_TOKEN_SESSION_KEY);
+
+ try {
+ if (Strings.isNonBlank(code)) {
+ return retrieveTokenForAuthCodeFromOauthServer(session, code);
+ } else if (Strings.isNonBlank(token)) {
+ // they have a token but no auth code and not or no longer authenticated;
+ // we need to check that the token is still valid
+ return validateTokenAgainstOauthServer(token);
+ } else {
+ // no token or code; the user needs to log in
+ return redirectUserToOauthLoginUi();
+ }
+ } catch (SecurityProviderDeniedAuthentication e) {
+ throw e;
+ } catch (Exception e) {
+ log.warn("Error performing OAuth: "+e, e);
+ throw Exceptions.propagate(e);
+ }
+ }
+
+ @Override
+ public boolean logout(HttpSession session) {
+ log.info("logout");
+ session.removeAttribute(OAUTH_ACCESS_TOKEN_SESSION_KEY);
+ session.removeAttribute(OAUTH_ACCESS_TOKEN_EXPIRY_UTC_KEY);
+ return true;
+ }
+
+ @Override
+ public boolean requiresUserPass() {
+ return false;
+ }
+
+ private boolean retrieveTokenForAuthCodeFromOauthServer(HttpSession session, String code) throws ClientProtocolException, IOException, ServletException, SecurityProviderDeniedAuthentication {
+ // get the access token by post to Google
+ HashMap<String, String> params = new HashMap<String, String>();
+ params.put(OAUTH_AUTH_CODE_PARAMETER_FOR_SERVER, code);
+ params.put("client_id", clientId);
+ params.put("client_secret", clientSecret);
+ params.put("redirect_uri", callbackUri);
+ params.put("grant_type", "authorization_code");
+
+ String body = post(uriGetToken, params);
+
+ Map<?,?> jsonObject = null;
+
+ // get the access token from json and request info from Google
+ try {
+ jsonObject = (Map<?,?>) Yamls.parseAll(body).iterator().next();
+ log.info("Parsed '"+body+"' as "+jsonObject);
+ } catch (Exception e) {
+ Exceptions.propagateIfFatal(e);
+ log.info("Unable to parse: '"+body+"'");
+ // throw new RuntimeException("Unable to parse json " + body);
+ return redirectUserToOauthLoginUi();
+ }
+
+ // TODO validate
+
+ // Put token in session
+ String accessToken = (String) jsonObject.get(accessTokenResponseKey);
+ session.setAttribute(OAUTH_ACCESS_TOKEN_SESSION_KEY, accessToken);
+
+ // TODO record code?
+// request.getSession().setAttribute(SESSION_KEY_CODE, code);
+
+ // TODO is it valid?
+ log.debug("Got token/code "+accessToken+"/"+code+" from "+jsonObject);
+ // eg Got token/code
+ // ya29.GluHBtzZ-R-CaoWMlso6KB6cq3DrbmwX6B3kjMmzWqzU-vO76WjKuNS3Ktog7vt9CJnxSZ63NmqO4p5bg20wl0-M14yO1LuoXNV5JX3qHDmXl2rl-z1LbCPEYJ-o
+ // / 4/yADFJRSRCxLgZFcpD_KU2jQiCXBGNHTsw0eGZqZ2t6IJJh2O1oWBnBDx4eWl4ZLCRAFJx3QjPYtl7LF9zj_DNlA
+ // from {
+ // access_token=ya29.GluHBtzZ-R-CaoWMlso6KB6cq3DrbmwX6B3kjMmzWqzU-vO76WjKuNS3Ktog7vt9CJnxSZ63NmqO4p5bg20wl0-M14yO1LuoXNV5JX3qHDmXl2rl-z1LbCPEYJ-o,
+ // expires_in=3600,
+ // refresh_token=1/b2Xk2rCVqKFsbz_xePv1tctvihnLoyo0YHsw4YQWK8M,
+ // scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.me,
+ // token_type=Bearer,
+ // id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc5NzhhOTEzNDcyNjFhMjkxYmQ3MWRjYWI0YTQ2NGJlN2QyNzk2NjYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiYXpwIjoiNzg5MTgyMDEyNTY1LWJ1cmQyNGgzYmMwaW03NGcycWVtaTdsbmlodmZxZDAyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiNzg5MTgyMDEyNTY1LWJ1cmQyNGgzYmMwaW03NGcycWVtaTdsbmlodmZxZDAyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTA2MDQyNTE3MjU2MTcxNzYyMTU0IiwiaGQiOiJjbG91ZHNvZnRjb3JwLmNvbSIsImVtYWlsIjoiYWxleC5oZW5ldmVsZEBjbG91ZHNvZnRjb3JwLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiQXpsdHo3YnR2Wk81eTZiMXMtVzRxdyIsImlhdCI6MTU0NjYyNDE2MiwiZXhwIjoxNTQ2NjI3NzYyfQ.E0NWILU7EEHL3GsveVFW1F91sml9iRceWpfVVc9blSKqafAcNwRl08JKT1FXUgfOUvgoYYj6IDIxT4L59-3CObNHS7RtbDJmIk0eWf_h8OFFGTTtd6P2-FTtM-6HVLKkMcKvJHHB07APsqeQj4o3zWY4G3f0QIX6bb424PwxwcDGS6gO8aA9cX2vVyr90h8FgtR9qnbYQxaSrcQNmEmPYHPZiOMzFoxR5WpXhtmPAFc4sMVFjrvQEf8s3GSr6ciMdC7BtKhfBII8s9iYV4LJCRQjxvsCzZ_PfWAZmExNaNOVltfoo5uGVmDPvzCUbSIoPmpj4jpPJVJ0fQtl7E6Tlg}
+ // TODO get the user's ID : https://stackoverflow.com/questions/24442668/google-oauth-api-to-get-users-email-address
+ String user = accessToken; // wrong, see above
+ session.setAttribute(BrooklynSecurityProviderFilterHelper.AUTHENTICATED_USER_SESSION_ATTRIBUTE, user);
+
+ return true;
+ }
+
+ private boolean validateTokenAgainstOauthServer(String token) throws ClientProtocolException, IOException {
+ // TODO support validation, and run periodically
+
+// HashMap<String, String> params = new HashMap<String, String>();
+// params.put(OAUTH_ACCESS_TOKEN_KEY, token);
+//
+// String body = post(uriTokenInfo, params);
+//
+// Map<?,?> jsonObject = null;
+// // get the access token from json and request info from Google
+// try {
+// jsonObject = (Map<?,?>) Yamls.parseAll(body).iterator().next();
+// LOG.info("Parsed '"+body+"' as "+jsonObject);
+// } catch (Exception e) {
+// Exceptions.propagateIfFatal(e);
+// LOG.info("Unable to parse: '"+body+"'");
+// throw new RuntimeException("Unable to parse json " + body, e);
+// }
+//
+// if (!clientId.equals(jsonObject.get(audience))) {
+// LOG.warn("Oauth not meant for this client ("+clientId+"), redirecting user to login again: "+jsonObject);
+// return redirectUserToOauthLoginUi();
+// }
+//
+// // TODO
+// // if (isTokenExpiredOrNearlySo(...) { ... }
+
+ return true;
+ }
+
+ // TODO these http methods need tidying
+
+ // makes a GET request to url and returns body as a string
+ public String get(String url) throws ClientProtocolException, IOException {
+ return execute(new HttpGet(url));
+ }
+
+ // makes a POST request to url with form parameters and returns body as a
+ // string
+ public String post(String url, Map<String, String> formParameters) throws ClientProtocolException, IOException {
+ HttpPost request = new HttpPost(url);
+
+ List<NameValuePair> nvps = new ArrayList<NameValuePair>();
+ for (String key : formParameters.keySet()) {
+ nvps.add(new BasicNameValuePair(key, formParameters.get(key)));
+ }
+ request.setEntity(new UrlEncodedFormEntity(nvps));
+
+ return execute(request);
+ }
+
+ // makes request and checks response code for 200
+ private String execute(HttpRequestBase request) throws ClientProtocolException, IOException {
+ // TODO tidy
+ HttpClient httpClient = new DefaultHttpClient();
+ HttpResponse response = httpClient.execute(request);
+
+ HttpEntity entity = response.getEntity();
+ String body = EntityUtils.toString(entity);
+
+ if (response.getStatusLine().getStatusCode() != 200) {
+ throw new RuntimeException(
+ "Expected 200 but got " + response.getStatusLine().getStatusCode() + ", with body " + body);
+ }
+
+ return body;
+ }
+
+ private boolean redirectUserToOauthLoginUi() throws IOException, SecurityProviderDeniedAuthentication {
+ String state=Identifiers.makeRandomId(12); //should be stored in session
+ StringBuilder oauthUrl = new StringBuilder().append(uriAuthorize)
+ .append("?response_type=").append("code")
+ .append("&client_id=").append(clientId) // the client id from the api console registration
+ .append("&redirect_uri=").append(callbackUri) // the servlet that github redirects to after
+ // authorization
+// .append("&scope=").append("user public_repo")
+ .append("&scope=openid%20email") // scope is the api permissions we
+ .append("&state=").append(state)
+ .append("&access_type=offline") // here we are asking to access to user's data while they are not
+ // signed in
+ .append("&approval_prompt=force"); // this requires them to verify which account to use, if they are
+ // already signed in
+
+ // just for look inside
+// Collection<String> originalHeaders = response.getHeaderNames();
+
+ throw new SecurityProviderDeniedAuthentication(
+ Response.status(Status.FOUND).header(HttpHeader.LOCATION.asString(), oauthUrl.toString()).build());
+// response.addHeader("Origin", "http://localhost.io:8081");
+// response.addHeader("Access-Control-Allow-Origin", "*");
+// response.addHeader("Access-Control-Request-Method", "GET, POST");
+// response.addHeader("Access-Control-Request-Headers", "origin, x-requested-with");
+ }
+
+ private Request getJettyRequest() {
+ return Optional.ofNullable(HttpConnection.getCurrentConnection())
+ .map(HttpConnection::getHttpChannel)
+ .map(HttpChannel::getRequest)
+ .orElse(null);
+ }
+
+}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
index 57d1400a73..0854c7f303 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
@@ -18,7 +18,10 @@
*/
package org.apache.brooklyn.rest.security.provider;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import javax.servlet.http.HttpSession;
+import javax.ws.rs.core.Response;
/**
* The SecurityProvider is responsible for doing authentication.
@@ -27,9 +30,31 @@
*/
public interface SecurityProvider {
- public boolean isAuthenticated(HttpSession session);
-
- public boolean authenticate(HttpSession session, String user, String password);
-
+ public boolean isAuthenticated(@Nullable HttpSession session);
+ /** whether this provider requires a user/pass; if this returns false, the framework can
+ * send null/null as the user/pass to {@link #authenticate(HttpSession, String, String)},
+ * and should do that if user/pass info is not immediately available
+ * (ie for things like oauth, the framework should not require basic auth if this method returns false)
+ */
+ public boolean requiresUserPass();
+ /** Perform the authentication. If {@link #requiresUserPass()} returns false, user/pass may be null;
+ * otherwise the framework will guarantee the basic auth is in effect and these values are set.
+ * The provider should not send a response but should throw {@link SecurityProviderDeniedAuthentication}
+ * if a custom response is required. It can include a response in that exception,
+ * e.g. to provide more information or supply a redirect. */
+ public boolean authenticate(@Nonnull HttpSession session, String user, String pass) throws SecurityProviderDeniedAuthentication;
public boolean logout(HttpSession session);
+
+ public static class SecurityProviderDeniedAuthentication extends Exception {
+ private static final long serialVersionUID = -3048228939219746783L;
+ private final Response response;
+ public SecurityProviderDeniedAuthentication() { this(null); }
+ public SecurityProviderDeniedAuthentication(Response r) {
+ this.response = r;
+ }
+ public Response getResponse() {
+ return response;
+ }
+ }
+
}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
index 46a22385c0..fc80fe89ee 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
@@ -24,13 +24,19 @@
import javax.ws.rs.ext.Provider;
import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
@Provider
// Needed by tests in rest-resources module and by main code in rest-server
public class ManagementContextProvider implements ContextResolver<ManagementContext> {
+
+ @SuppressWarnings("unused")
+ private static final Logger log = LoggerFactory.getLogger(ManagementContextProvider.class);
+
@Context
private ServletContext context;
@@ -38,6 +44,11 @@
public ManagementContextProvider() {
}
+
+ // for usages that cannot do injection
+ public ManagementContextProvider(ServletContext context) {
+ this.context = context;
+ }
@VisibleForTesting
public ManagementContextProvider(ManagementContext mgmt) {
@@ -47,14 +58,18 @@ public ManagementContextProvider(ManagementContext mgmt) {
@Override
public ManagementContext getContext(Class<?> type) {
if (type == ManagementContext.class) {
- if (mgmt != null) {
- return mgmt;
- } else {
- return (ManagementContext) context.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
- }
+ return getManagementContext();
} else {
return null;
}
}
+
+ @Beta
+ public ManagementContext getManagementContext() {
+ if (mgmt != null) {
+ return mgmt;
+ }
+ return OsgiCompat.getManagementContext(context);
+ }
}
diff --git a/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml b/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml
index 5fe7f02da4..1f10b4bafa 100644
--- a/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml
+++ b/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml
@@ -34,11 +34,11 @@ limitations under the License.
<cm:property-placeholder persistent-id="org.apache.brooklyn.rest.filter.cors">
<cm:default-properties>
- <cm:property name="cors.enabled" value="false"/>
+ <cm:property name="cors.enabled" value="true"/>
<cm:property name="cors.allow.origins" value=""/>
<cm:property name="cors.allow.headers" value=""/>
<cm:property name="cors.expose.headers" value=""/>
- <cm:property name="cors.allow.credentials" value="false"/>
+ <cm:property name="cors.allow.credentials" value="true"/>
<cm:property name="cors.max.age" value="-1"/>
<cm:property name="cors.preflight.error.status" value="200"/>
<cm:property name="cors.block.if.unauthorized" value="false"/>
@@ -59,16 +59,8 @@ limitations under the License.
<reference id="localManagementContext"
interface="org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal"/>
- <jaas:config name="webconsole">
- <jaas:module className="org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule" flags="required"/>
- </jaas:config>
-
<reference id="shutdownHandler" interface="org.apache.brooklyn.core.mgmt.ShutdownHandler"/>
- <bean class="org.apache.brooklyn.rest.security.jaas.ManagementContextHolder">
- <property name="managementContext" ref="localManagementContext"/>
- </bean>
-
<bean id="accessResourceBean" class="org.apache.brooklyn.rest.resources.AccessResource"/>
<bean id="activityResourceBean" class="org.apache.brooklyn.rest.resources.ActivityResource"/>
<bean id="adjunctResourceBean" class="org.apache.brooklyn.rest.resources.AdjunctResource"/>
@@ -116,17 +108,10 @@ limitations under the License.
<bean class="org.apache.brooklyn.rest.util.DefaultExceptionMapper"/>
<bean class="org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider"/>
<bean class="org.apache.brooklyn.rest.util.FormMapProvider"/>
- <bean class="org.apache.cxf.jaxrs.security.JAASAuthenticationFilter">
- <property name="contextName" value="webconsole"/>
- <property name="realmName" value="webconsole"/>
- </bean>
<bean class="org.apache.brooklyn.rest.util.ManagementContextProvider">
<argument ref="localManagementContext"/>
</bean>
- <!--
- TODO ShutdownHandlerProvider, sync with init work.
- Needs to be custom OSGi implementation?
- -->
+ <bean class="org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilterJersey"/>
<bean class="org.apache.brooklyn.rest.filter.CsrfTokenFilter"/>
<bean class="org.apache.brooklyn.rest.filter.RequestTaggingRsFilter"/>
<bean class="org.apache.brooklyn.rest.filter.NoCacheFilter"/>
diff --git a/rest/rest-resources/src/main/resources/jaas.conf b/rest/rest-resources/src/main/resources/jaas.conf
deleted file mode 100644
index bb18334158..0000000000
--- a/rest/rest-resources/src/main/resources/jaas.conf
+++ /dev/null
@@ -1,21 +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.
- */
-webconsole {
- org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule required;
-};
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/filter/EntitlementContextFilterTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/filter/EntitlementContextFilterTest.java
index 0d37cc8610..b266bd4163 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/filter/EntitlementContextFilterTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/filter/EntitlementContextFilterTest.java
@@ -29,13 +29,12 @@
import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
import org.apache.brooklyn.core.mgmt.entitlement.WebEntitlementContext;
import org.apache.brooklyn.rest.BrooklynWebConfig;
-import org.apache.brooklyn.rest.security.jaas.JaasUtils;
import org.apache.brooklyn.rest.security.provider.ExplicitUsersSecurityProvider;
import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.cxf.interceptor.security.JAASLoginInterceptor;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.http.HttpStatus;
+import org.eclipse.jetty.server.session.SessionHandler;
import org.testng.annotations.Test;
public class EntitlementContextFilterTest extends BrooklynRestResourceTest {
@@ -58,18 +57,13 @@ protected void configureCXF(JAXRSServerFactoryBean sf) {
props.put(BrooklynWebConfig.PASSWORD_FOR_USER(USER_PASS), USER_PASS);
getManagementContext().getScratchpad().put(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE, new ExplicitUsersSecurityProvider(getManagementContext()));
+ sf.setProvider(new SessionHandler());
super.configureCXF(sf);
-
- JaasUtils.init(getManagementContext());
-
- JAASLoginInterceptor jaas = new JAASLoginInterceptor();
- jaas.setContextName("webconsole");
- sf.getInInterceptors().add(jaas);
-
}
@Override
protected void addBrooklynResources() {
+ addResource(new BrooklynSecurityProviderFilterJersey());
addResource(new RequestTaggingRsFilter());
addResource(new EntitlementContextFilter());
addResource(new EntitlementResource());
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/jaas/BrooklynLoginModuleTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/jaas/BrooklynLoginModuleTest.java
deleted file mode 100644
index f33e80789d..0000000000
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/jaas/BrooklynLoginModuleTest.java
+++ /dev/null
@@ -1,195 +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 org.apache.brooklyn.rest.security.jaas;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
-
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.FailedLoginException;
-import javax.security.auth.login.LoginException;
-
-import org.apache.brooklyn.core.internal.BrooklynProperties;
-import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport;
-import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.rest.BrooklynWebConfig;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-// http://docs.oracle.com/javase/7/docs/technotes/guides/security/jaas/JAASLMDevGuide.html
-public class BrooklynLoginModuleTest extends BrooklynMgmtUnitTestSupport {
- private static final String ACCEPTED_USER = "user";
- private static final String ACCEPTED_PASSWORD = "password";
- private static final String DEFAULT_ROLE = "webconsole";
- private CallbackHandler GOOD_CB_HANDLER = new TestCallbackHandler(
- ACCEPTED_USER,
- ACCEPTED_PASSWORD);
- private CallbackHandler BAD_CB_HANDLER = new TestCallbackHandler(
- ACCEPTED_USER + ".invalid",
- ACCEPTED_PASSWORD + ".invalid");
-
- private Subject subject;
- private Map<String, ?> sharedState;
- private Map<String, ?> options;
-
- private BrooklynLoginModule module;
-
- @Override
- @BeforeMethod(alwaysRun = true)
- public void setUp() throws Exception {
- BrooklynProperties properties = BrooklynProperties.Factory.newEmpty();
- properties.addFrom(ImmutableMap.of(
- BrooklynWebConfig.USERS, ACCEPTED_USER,
- BrooklynWebConfig.PASSWORD_FOR_USER("user"), ACCEPTED_PASSWORD));
- mgmt = LocalManagementContextForTests.builder(true).useProperties(properties).build();
- ManagementContextHolder.setManagementContextStatic(mgmt);
-
- super.setUp();
-
- subject = new Subject();
- sharedState = MutableMap.of();
- options = ImmutableMap.of();
-
- module = new BrooklynLoginModule();
- }
-
- @Test
- public void testMissingCallback() throws LoginException {
- module.initialize(subject, null, sharedState, options);
- try {
- module.login();
- fail("Login is supposed to fail due to missing callback");
- } catch (FailedLoginException e) {
- // Expected, ignore
- }
- assertFalse(module.commit(), "commit");
- assertEmptyPrincipals();
- assertFalse(module.abort(), "abort");
- }
-
- @Test
- public void testFailedLoginCommitAbort() throws LoginException {
- badLogin();
- assertFalse(module.commit(), "commit");
- assertEmptyPrincipals();
- assertFalse(module.abort(), "abort");
- }
-
- @Test
- public void testFailedLoginCommitAbortReadOnly() throws LoginException {
- subject.setReadOnly();
- badLogin();
- assertFalse(module.commit(), "commit");
- assertEmptyPrincipals();
- assertFalse(module.abort(), "abort");
- }
-
- @Test
- public void testFailedLoginAbort() throws LoginException {
- badLogin();
- assertFalse(module.abort(), "abort");
- assertEmptyPrincipals();
- }
-
- @Test
- public void testSuccessfulLoginCommitLogout() throws LoginException {
- goodLogin();
- assertTrue(module.commit(), "commit");
- assertBrooklynPrincipal();
- assertTrue(module.logout(), "logout");
- assertEmptyPrincipals();
- }
-
- @Test
- public void testSuccessfulLoginCommitAbort() throws LoginException {
- goodLogin();
- assertTrue(module.commit(), "commit");
- assertBrooklynPrincipal();
- assertTrue(module.abort(), "logout");
- assertEmptyPrincipals();
- }
-
- @Test
- public void testSuccessfulLoginCommitAbortReadOnly() throws LoginException {
- subject.setReadOnly();
- goodLogin();
- try {
- module.commit();
- fail("Commit expected to throw");
- } catch (LoginException e) {
- // Expected
- }
- assertTrue(module.abort());
- }
-
- @Test
- public void testSuccessfulLoginAbort() throws LoginException {
- goodLogin();
- assertTrue(module.abort(), "abort");
- assertEmptyPrincipals();
- }
-
- @Test
- public void testCustomRole() throws LoginException {
- String role = "users";
- options = ImmutableMap.<String, Object>of(BrooklynLoginModule.PROPERTY_ROLE, role);
- goodLogin();
- assertTrue(module.commit(), "commit");
- assertBrooklynPrincipal(role);
- }
-
- private void goodLogin() throws LoginException {
- module.initialize(subject, GOOD_CB_HANDLER, sharedState, options);
- assertTrue(module.login(), "login");
- assertEmptyPrincipals();
- }
-
- private void badLogin() throws LoginException {
- module.initialize(subject, BAD_CB_HANDLER, sharedState, options);
- try {
- module.login();
- fail("Login is supposed to fail due to invalid username+password pair");
- } catch (FailedLoginException e) {
- // Expected, ignore
- }
- }
-
- private void assertBrooklynPrincipal() {
- assertBrooklynPrincipal(DEFAULT_ROLE);
- }
- private void assertBrooklynPrincipal(String role) {
- assertEquals(subject.getPrincipals(), ImmutableSet.of(
- new BrooklynLoginModule.UserPrincipal(ACCEPTED_USER),
- new BrooklynLoginModule.RolePrincipal(role)));
- }
-
- private void assertEmptyPrincipals() {
- assertEquals(subject.getPrincipals().size(), 0);
- }
-
-}
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/jaas/TestCallbackHandler.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/jaas/TestCallbackHandler.java
deleted file mode 100644
index 4854196523..0000000000
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/jaas/TestCallbackHandler.java
+++ /dev/null
@@ -1,50 +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 org.apache.brooklyn.rest.security.jaas;
-
-import java.io.IOException;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-public class TestCallbackHandler implements CallbackHandler {
- private String username;
- private String password;
-
- public TestCallbackHandler(String username, String password) {
- this.username = username;
- this.password = password;
- }
-
- @Override
- public void handle(Callback[] callbacks)
- throws IOException, UnsupportedCallbackException {
- for (Callback cb : callbacks) {
- if (cb instanceof NameCallback) {
- ((NameCallback)cb).setName(username);
- } else if (cb instanceof PasswordCallback) {
- ((PasswordCallback)cb).setPassword(password.toCharArray());
- }
- }
- }
-
-}
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
index 54c9384419..de9199efac 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
@@ -55,7 +55,9 @@
protected TestShutdownHandler shutdownListener = createShutdownHandler();
protected final static String ENDPOINT_ADDRESS_LOCAL = "local://";
- protected final static String ENDPOINT_ADDRESS_HTTP = "http://localhost:9998/";
+ protected final static String ENDPOINT_ADDRESS_HOST = "localhost";
+ protected final static int ENDPOINT_ADDRESS_PORT = 9998;
+ protected final static String ENDPOINT_ADDRESS_HTTP = "http://"+ENDPOINT_ADDRESS_HOST+":"+ENDPOINT_ADDRESS_PORT+"/";
protected Set<Class<?>> resourceClasses;
protected Set<Object> resourceBeans;
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
index 183f3de9fd..a1c1067f07 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
@@ -43,10 +43,13 @@
import org.apache.brooklyn.util.net.Urls;
import org.apache.brooklyn.util.repeat.Repeater;
import org.apache.brooklyn.util.time.Duration;
+import org.apache.cxf.BusFactory;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.jaxrs.utils.ResourceUtils;
+import org.apache.cxf.transport.http_jetty.JettyHTTPServerEngine;
+import org.apache.cxf.transport.http_jetty.JettyHTTPServerEngineFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
@@ -58,7 +61,8 @@
private static final Logger log = LoggerFactory.getLogger(BrooklynRestResourceTest.class);
- private static Server server;
+ private JettyHTTPServerEngine serverEngine;
+ private Server server;
protected List<?> clientProviders;
class DefaultTestApp extends javax.ws.rs.core.Application {
@@ -89,7 +93,12 @@ protected void destroyClass() throws Exception {
protected synchronized void startServer() throws Exception {
if (server == null) {
setUpResources();
- JAXRSServerFactoryBean sf = ResourceUtils.createApplication(createRestApp(), true);
+
+ // needed to enable session support
+ serverEngine = new JettyHTTPServerEngineFactory().createJettyHTTPServerEngine(
+ ENDPOINT_ADDRESS_HOST, ENDPOINT_ADDRESS_PORT, "http");
+ serverEngine.setSessionSupport(true);
+ JAXRSServerFactoryBean sf = ResourceUtils.createApplication(createRestApp(), true,false,false, BusFactory.getDefaultBus());
if (clientProviders == null) {
clientProviders = sf.getProviders();
}
@@ -115,6 +124,10 @@ public synchronized void stopServer() throws Exception {
server.destroy();
server = null;
}
+ if (serverEngine!=null) {
+ serverEngine.shutdown();
+ serverEngine = null;
+ }
}
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java
deleted file mode 100644
index f5089a148e..0000000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java
+++ /dev/null
@@ -1,179 +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 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.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
-import org.apache.brooklyn.core.mgmt.entitlement.WebEntitlementContext;
-import org.apache.brooklyn.rest.BrooklynWebConfig;
-import org.apache.brooklyn.rest.resources.LogoutResource;
-import org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule;
-import org.apache.brooklyn.rest.security.provider.DelegatingSecurityProvider;
-import org.apache.brooklyn.rest.util.OsgiCompat;
-import org.apache.brooklyn.util.text.Strings;
-import org.apache.commons.codec.binary.Base64;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Provides basic HTTP authentication.
- *
- * @deprecated since 0.9.0, use JAAS authentication instead, see {@link BrooklynLoginModule}, {@link LogoutResource}, {@link EntitlementContextFilter}.
- */
-@Deprecated
-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 = BrooklynLoginModule.AUTHENTICATED_USER_SESSION_ATTRIBUTE;
-
- 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(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE, request.getRemoteAddr());
- String user = null, pass = null;
- String authorization = request.getHeader("Authorization");
- if (authorization != null) {
- String userpass = new String(Base64.decodeBase64(authorization.substring(6)));
- int idxColon = userpass.indexOf(":");
- if (idxColon >= 0) {
- user = userpass.substring(0, idxColon);
- pass = userpass.substring(idxColon + 1);
- } else {
- return false;
- }
- }
- 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 = OsgiCompat.getManagementContext(config.getServletContext());
- 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(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE);
- request.getSession().invalidate();
- }
-
-}
diff --git a/rest/rest-server/src/main/resources/web-security.xml b/rest/rest-server/src/main/resources/web-security.xml
index 231145878c..c6a3974eae 100644
--- a/rest/rest-server/src/main/resources/web-security.xml
+++ b/rest/rest-server/src/main/resources/web-security.xml
@@ -21,31 +21,6 @@
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
- <security-constraint>
- <web-resource-collection>
- <web-resource-name>Logout</web-resource-name>
- <url-pattern>/v1/logout</url-pattern>
- </web-resource-collection>
- </security-constraint>
-
- <!-- Ignored programmatically if noConsoleSecurity -->
- <security-constraint>
- <web-resource-collection>
- <web-resource-name>All</web-resource-name>
- <url-pattern>/</url-pattern>
- </web-resource-collection>
- <auth-constraint>
- <role-name>webconsole</role-name>
- </auth-constraint>
- </security-constraint>
-
- <login-config>
- <auth-method>BASIC</auth-method>
- <realm-name>webconsole</realm-name>
- </login-config>
-
- <security-role>
- <role-name>webconsole</role-name>
- </security-role>
-
+ <!-- now done with filters -->
+
</web-app>
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
index eaa20f4bf2..7063531a9b 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
@@ -38,16 +38,7 @@
import org.apache.brooklyn.core.server.BrooklynServerConfig;
import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.rest.filter.CorsImplSupplierFilter;
-import org.apache.brooklyn.rest.filter.CsrfTokenFilter;
-import org.apache.brooklyn.rest.filter.EntitlementContextFilter;
-import org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter;
-import org.apache.brooklyn.rest.filter.LoggingFilter;
-import org.apache.brooklyn.rest.filter.NoCacheFilter;
-import org.apache.brooklyn.rest.filter.RequestTaggingFilter;
-import org.apache.brooklyn.rest.filter.RequestTaggingRsFilter;
-import org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule.RolePrincipal;
-import org.apache.brooklyn.rest.security.jaas.JaasUtils;
+import org.apache.brooklyn.rest.filter.*;
import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
import org.apache.brooklyn.rest.security.provider.SecurityProvider;
import org.apache.brooklyn.rest.util.ManagementContextProvider;
@@ -58,7 +49,6 @@
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.net.Networking;
import org.apache.brooklyn.util.text.WildcardGlobs;
-import org.eclipse.jetty.jaas.JAASLoginService;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
@@ -218,6 +208,7 @@ private WebAppContext servletContextHandler(ManagementContext managementContext)
new ShutdownHandlerProvider(shutdownListener),
new RequestTaggingRsFilter(),
new NoCacheFilter(),
+ new BrooklynSecurityProviderFilterJersey(),
new HaHotCheckResourceFilter(),
new EntitlementContextFilter(),
new CsrfTokenFilter());
@@ -281,7 +272,7 @@ private static Server startServer(ManagementContext mgmt, ContextHandler context
private static Server startServer(ManagementContext mgmt, ContextHandler context, String summary, InetSocketAddress bindLocation) {
Server server = new Server(bindLocation);
- initJaas(mgmt, server);
+ initAuth(mgmt, server);
server.setHandler(context);
try {
@@ -296,17 +287,8 @@ private static Server startServer(ManagementContext mgmt, ContextHandler context
}
// TODO Why parallel code for server init here and in BrooklynWebServer?
- private static void initJaas(ManagementContext mgmt, Server server) {
- JaasUtils.init(mgmt);
- initJaasLoginService(server);
- }
-
- public static void initJaasLoginService(Server server) {
- JAASLoginService loginService = new JAASLoginService();
- loginService.setName("webconsole");
- loginService.setLoginModuleName("webconsole");
- loginService.setRoleClassNames(new String[] {RolePrincipal.class.getName()});
- server.addBean(loginService);
+ private static void initAuth(ManagementContext mgmt, Server server) {
+ server.addBean(new BrooklynSecurityProviderFilterHelper());
}
public static BrooklynRestApiLauncher launcher() {
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java
index b7264b2668..5ed763d9ae 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java
@@ -20,8 +20,11 @@
import javax.servlet.http.HttpSession;
+import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
import org.apache.brooklyn.rest.security.provider.SecurityProvider;
+/** allows anyone to access, but does require a non-null user (any password) to be supplied via Basic auth,
+ * in contrast to {@link AnyoneSecurityProvider} */
public class AuthenticateAnyoneSecurityProvider implements SecurityProvider {
@Override
@@ -38,4 +41,9 @@ public boolean authenticate(HttpSession session, String user, String password) {
public boolean logout(HttpSession session) {
return false;
}
-}
\ No newline at end of file
+
+ @Override
+ public boolean requiresUserPass() {
+ return true;
+ }
+}
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java
index cad251f0f3..d6350d01d6 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java
@@ -43,4 +43,9 @@ public boolean authenticate(HttpSession session, String user, String password) {
public boolean logout(HttpSession session) {
return false;
}
+
+ @Override
+ public boolean requiresUserPass() {
+ return true;
+ }
}
diff --git a/software/base/pom.xml b/software/base/pom.xml
index 426012035a..1e66329e2c 100644
--- a/software/base/pom.xml
+++ b/software/base/pom.xml
@@ -158,6 +158,11 @@
<artifactId>mx4j-tools</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-core</artifactId>
+ <version>${cxf.version}</version>
+ </dependency>
</dependencies>
<build>
diff --git a/software/winrm/pom.xml b/software/winrm/pom.xml
index b4a3f7eec9..8882c03719 100644
--- a/software/winrm/pom.xml
+++ b/software/winrm/pom.xml
@@ -52,6 +52,14 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
+ <exclusion>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-core</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-transports-http</artifactId>
+ </exclusion>
</exclusions>
</dependency>
<!--
diff --git a/utils/rt-felix/src/main/java/org/apache/brooklyn/rt/felix/EmbeddedFelixFramework.java b/utils/rt-felix/src/main/java/org/apache/brooklyn/rt/felix/EmbeddedFelixFramework.java
index f8152723ed..42b7e142dc 100644
--- a/utils/rt-felix/src/main/java/org/apache/brooklyn/rt/felix/EmbeddedFelixFramework.java
+++ b/utils/rt-felix/src/main/java/org/apache/brooklyn/rt/felix/EmbeddedFelixFramework.java
@@ -15,7 +15,6 @@
*/
package org.apache.brooklyn.rt.felix;
-import com.google.common.base.Stopwatch;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -24,13 +23,16 @@
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
+
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.exceptions.Exceptions;
@@ -48,6 +50,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Stopwatch;
+
/**
* Functions for starting an Apache Felix OSGi framework inside a non-OSGi Brooklyn distro.
*
@@ -61,6 +65,17 @@
private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
private static final Set<String> SYSTEM_BUNDLES = MutableSet.of();
+ private static final Set<URL> BOOT_BUNDLES;
+
+ static {
+ try {
+ BOOT_BUNDLES = MutableSet.copyOf(Collections.list(
+ EmbeddedFelixFramework.class.getClassLoader().getResources(MANIFEST_PATH))).asUnmodifiable();
+ } catch (Exception e) {
+ // should never happen; weird classloading problem
+ throw Exceptions.propagate(e);
+ }
+ }
// -------- creating
@@ -132,16 +147,17 @@ public static void stopFramework(Framework framework) throws RuntimeException {
private static void installBootBundles(Framework framework) {
Stopwatch timer = Stopwatch.createStarted();
LOG.debug("Installing OSGi boot bundles from "+EmbeddedFelixFramework.class.getClassLoader()+"...");
- Enumeration<URL> resources;
- try {
- resources = EmbeddedFelixFramework.class.getClassLoader().getResources(MANIFEST_PATH);
- } catch (IOException e) {
- throw Exceptions.propagate(e);
- }
+
+ Iterator<URL> resources = BOOT_BUNDLES.iterator();
+ // previously we evaluated this each time, but lately (discovered in 2019,
+ // possibly the case for a long time before) it seems to grow, accessing ad hoc dirs
+ // in cache/* made by tests, which get deleted, logging lots of errors.
+ // so now we statically populate it at load time.
+
BundleContext bundleContext = framework.getBundleContext();
Map<String, Bundle> installedBundles = getInstalledBundlesById(bundleContext);
- while(resources.hasMoreElements()) {
- URL url = resources.nextElement();
+ while (resources.hasNext()) {
+ URL url = resources.next();
ReferenceWithError<?> installResult = installExtensionBundle(bundleContext, url, installedBundles, OsgiUtils.getVersionedId(framework));
if (installResult.hasError() && !installResult.masksErrorIfPresent()) {
// it's reported as a critical error, so warn here
With regards,
Apache Git Services