This is an automated email from the ASF dual-hosted git repository. heneveld pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git
commit 5e1bf35fa4f97204d8007686191688d5ba6f9c08 Author: Alex Heneveld <[email protected]> AuthorDate: Mon Jan 7 17:14:55 2019 +0000 working filter, sharing sessions lots of tidies needed but minimal confirmation here that we can share auth across servlets by discovering each other's sessions --- .../brooklyn/launcher/BrooklynWebServer.java | 43 +--- .../filter/BrooklynSecurityProviderFilter.java | 254 --------------------- .../BrooklynSecurityProviderFilterHelper.java | 218 ++++++++++++++++++ .../BrooklynSecurityProviderFilterJavax.java | 88 +++++++ .../BrooklynSecurityProviderFilterJersey.java | 71 ++++++ .../provider/DelegatingSecurityProvider.java | 2 +- .../security/provider/OauthSecurityProvider.java | 204 +++++++---------- .../rest/security/provider/SecurityProvider.java | 24 +- .../rest/util/ManagementContextProvider.java | 27 ++- .../main/resources/OSGI-INF/blueprint/service.xml | 6 +- .../rest/filter/EntitlementContextFilterTest.java | 6 +- .../rest/testing/BrooklynRestResourceTest.java | 1 - .../org/apache/brooklyn/rest/RestApiSetup.java | 17 -- .../filter/BrooklynPropertiesSecurityFilter.java | 179 --------------- .../apache/brooklyn/rest/filter/MyOauthFilter.java | 244 -------------------- .../brooklyn/rest/BrooklynRestApiLauncher.java | 19 +- 16 files changed, 521 insertions(+), 882 deletions(-) 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 ad50454..7e6a0bb 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.List; 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; @@ -53,10 +52,15 @@ import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocati import org.apache.brooklyn.rest.BrooklynWebConfig; import org.apache.brooklyn.rest.NopSecurityHandler; import org.apache.brooklyn.rest.RestApiSetup; -import org.apache.brooklyn.rest.filter.*; -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.filter.BrooklynSecurityProviderFilterJavax; +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.util.ManagementContextProvider; import org.apache.brooklyn.rest.util.ShutdownHandlerProvider; import org.apache.brooklyn.util.collections.MutableMap; @@ -81,7 +85,6 @@ import org.apache.brooklyn.util.time.Duration; 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; @@ -193,14 +196,6 @@ public class BrooklynWebServer { 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.BrooklynSecurityProviderFilter> securityFilterClazz; - @SetFromFlag private boolean skipSecurity = false; @@ -223,7 +218,6 @@ public class BrooklynWebServer { log.warn("Ignoring unknown flags " + leftovers); webappTempDir = BrooklynServerPaths.getBrooklynWebTmpDir(managementContext); - JaasUtils.init(managementContext); } public BrooklynWebServer(ManagementContext managementContext, int port) { @@ -234,12 +228,6 @@ public class BrooklynWebServer { 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.BrooklynSecurityProviderFilter> filterClazz) { - this.securityFilterClazz = filterClazz; - } - public BrooklynWebServer skipSecurity() { return skipSecurity(true); } @@ -390,13 +378,6 @@ public class BrooklynWebServer { 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()) { @@ -493,12 +474,8 @@ public class BrooklynWebServer { providersListBuilder.build().toArray()); RestApiSetup.installServletFilters(context, RequestTaggingFilter.class, + BrooklynSecurityProviderFilterJavax.class, LoggingFilter.class); - RestApiSetup.installOauthServletFilters(context, - MyOauthFilter.class); - if (securityFilterClazz != null) { - RestApiSetup.installServletFilters(context, securityFilterClazz); - } return context; } diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilter.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilter.java deleted file mode 100644 index 4f960f6..0000000 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilter.java +++ /dev/null @@ -1,254 +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 java.security.Principal; -import java.util.function.Function; - -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.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.SecurityContext; -import javax.ws.rs.ext.ContextResolver; -import javax.ws.rs.ext.Provider; - -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.PostAuthenticator; -import org.apache.commons.codec.binary.Base64; -import org.apache.http.auth.BasicUserPrincipal; -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 {@link BrooklynLoginModule} because that login module requires - * Basic auth, which is not flexible enough to support redirect-based solutions like Oauth. - */ -@Provider -@Priority(100) -public class BrooklynSecurityProviderFilter implements ContainerRequestFilter { - - /** - * 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 final Logger log = LoggerFactory.getLogger(BrooklynSecurityProviderFilter.class); - - - @Context - HttpServletRequest webRequest; - - @Context - private ContextResolver<ManagementContext> mgmtC; - - private ManagementContext mgmt() { - return mgmtC.getContext(ManagementContext.class); - } - - public static class SimpleSecurityContext implements SecurityContext { - final Principal principal; - final Function<String,Boolean> roleChecker; - final boolean secure; - final String authScheme; - - public SimpleSecurityContext(String username, Function<String, Boolean> roleChecker, boolean secure, String authScheme) { - this.principal = new BasicUserPrincipal(username); - this.roleChecker = roleChecker; - this.secure = secure; - this.authScheme = authScheme; - } - - @Override - public Principal getUserPrincipal() { - return principal; - } - - @Override - public boolean isUserInRole(String role) { - return roleChecker.apply(role); - } - - @Override - public boolean isSecure() { - return secure; - } - - @Override - public String getAuthenticationScheme() { - return authScheme; - } - } - - @Override - public void filter(ContainerRequestContext requestContext) throws IOException { - SecurityProvider provider = getProvider(); - HttpSession session = webRequest.getSession(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 { - abort(requestContext, "Invalid authorization string"); - return; - } - } else { - abort(requestContext, "Authorization required"); - return; - } - } - - if (session==null) { - // only create the session if an auth string is supplied - session = webRequest.getSession(true); - } - session.setAttribute(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE, webRequest.getRemoteAddr()); - - if (provider.authenticate(session, user, pass)) { - if (provider instanceof PostAuthenticator) { - ((PostAuthenticator)provider).postAuthenticate(requestContext); - } - if (user != null) { - session.setAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE, user); - if (requestContext.getSecurityContext().getUserPrincipal()==null) { - requestContext.setSecurityContext(new SimpleSecurityContext(user, (role) -> false, - webRequest.isSecure(), SecurityContext.BASIC_AUTH)); - } - } - return; - } - - abort(requestContext, "Authentication failed"); - return; - } - - private void abort(ContainerRequestContext requestContext, String message) { - requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED) - .type(MediaType.TEXT_PLAIN) - .entity(message) - .header("WWW-Authenticate", "Basic realm=\"brooklyn\"") - .build()); - } - - protected SecurityProvider getProvider() { - // we don't cache here (could, it might be faster) but the delegate does use a cache - return new DelegatingSecurityProvider(mgmt()); - } - -// 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) { -// -// 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-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 0000000..47180c9 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterHelper.java @@ -0,0 +1,218 @@ +/* + * 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.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"; + + // TODO ugly, using a static, but it shares across bundles and all have different instances, so this is reasonable + public static Set<SessionHandler> SESSION_MANAGER_CACHE = MutableSet.of(); + + private static final Logger log = LoggerFactory.getLogger(BrooklynSecurityProviderFilterHelper.class); + + /* 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.info("SESSION for "+ webRequest.getRequestURI()+", wants session "+requestedSessionId); + + if (webRequest instanceof Request) { + SessionHandler sm = ((Request)webRequest).getSessionHandler(); + boolean added = SESSION_MANAGER_CACHE.add( sm ); + log.info("SESSION MANAGER found for "+webRequest.getRequestURI()+": "+sm+" ("+added+")"); + } else { + log.info("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.info("SESSION found for "+webRequest.getRequestURI()+": "+s+"; "+m.isValid(s)); + return s; + } + } + } + + if (create) { + HttpSession session = webRequest.getSession(true); + log.info("SESSION creating for "+webRequest.getRequestURI()+": "+session); + return session; + } + + return null; + +// HttpSession session = webRequest.getSession(false); +// if (session!=null) return session; +// +// // go through all the known session managers +// +// +// webRequest.getServletContext().getServlets().nextElement().getServletConfig().getServletContext().getser +// BundleContext ctx = (BundleContext) webRequest.getServletContext().getAttribute( +// //WebContainerConstants.BUNDLE_CONTEXT_ATTRIBUTE +// "osgi-bundlecontext"); +// log.info("TEST context "+ctx); +// if (ctx!=null) { +// log.info("TEST server "+ctx.getServiceReference( "org.ops4j.pax.web.service.WebContainer" )); +// } +// ctx.getServiceReference( "org.ops4j.pax.web.service.WebContainer" ); +// +// ctx = FrameworkUtil.getBundle(BrooklynSecurityProviderFilterHelper.class).getBundleContext(); +// +// +// log.info("TEST context2 "+ctx); +// if (ctx!=null) { +// log.info("TEST server2 "+ctx.getServiceReference( "org.ops4j.pax.web.service.WebContainer" )); +// } +// +//// String id = webRequest.getRequestedSessionId(); +//// webRequest.getServletContext().gethan +////// for (Cookie c: webRequest.getCookies()) { +////// if ("JSESSIONID".equals(c.getName())) { +////// c.getValue() +////// } +////// } +//// HttpSession session = getLocalSession(id); +//// if (session == null) { +//// for (SessionHandler manager: getSessionIdManager().getSessionHandlers()) { +//// if (manager.equals(this) || !(manager instanceof CustomSessionHandler)) { +//// continue; +//// } +//// session = ((CustomSessionHandler)manager).getLocalSession(id); +//// if (session != null) { +//// break; +//// } +//// } // should we duplicate sessions in each context? // will we end up with inconsistent sessions? /* if (externalSession != null) { try { getSessionCache().put(id, externalSession); } catch (Exception e) { LOG.warn("Unable to save session to local cache."); } } */ } +//// } +//// return session; +// return null; + } + + public void run(HttpServletRequest webRequest, ManagementContext mgmt) throws SecurityProviderDeniedAuthentication { + log.info("SEC PROV for "+webRequest.getRequestURI()); + + 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=\"brooklyn\""); + } + 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 0000000..5751872 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJavax.java @@ -0,0 +1,88 @@ +/* + * 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 { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(BrooklynSecurityProviderFilterJavax.class); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // no init needed + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + try { + 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) { + HttpServletResponse rout = ((HttpServletResponse)response); + Response rin = e.getResponse(); + if (rin==null) rin = Response.status(Status.UNAUTHORIZED).build(); + + rout.setStatus(rin.getStatus()); + + // TODO does content type need to be set explicitly? + 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 + } + +} \ 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 0000000..ff1a1d3 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJersey.java @@ -0,0 +1,71 @@ +/* + * 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.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; + +/** See {@link BrooklynSecurityProviderFilterHelper} */ +@Provider +@Priority(100) +public class BrooklynSecurityProviderFilterJersey implements ContainerRequestFilter { + + @Context + HttpServletRequest webRequest; + + @Context + private ContextResolver<ManagementContext> mgmtC; + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + 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) { + rin = Response.status(Status.UNAUTHORIZED).entity("Authentication is required at "+location). + header(HttpHeader.LOCATION.asString(), location).build(); + } else { + 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/security/provider/DelegatingSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java index 664c5c1..65567c4 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 @@ -195,7 +195,7 @@ public class DelegatingSecurityProvider implements SecurityProvider { } @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()); 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 index b3975f1..a3bbcf1 100644 --- 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 @@ -27,12 +27,14 @@ import java.util.Optional; import javax.servlet.ServletException; import javax.servlet.http.HttpSession; -import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; -import org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilter.SimpleSecurityContext; +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; @@ -46,83 +48,59 @@ 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.eclipse.jetty.server.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Configurable OAuth redirect security provider * * Redirects all inbound requests to an oath web server unless a session token is specified. */ -public class OauthSecurityProvider implements SecurityProvider, SecurityProvider.PostAuthenticator { +public class OauthSecurityProvider implements SecurityProvider { public static final Logger LOG = LoggerFactory.getLogger(OauthSecurityProvider.class); - // TODO replace with LOG - private static final Logger logger = LoggerFactory.getLogger(OauthSecurityProvider.class); - private static final String SESSION_KEY_ACCESS_TOKEN = "access_token"; - private static final String SESSION_KEY_CODE = "code"; - private static final String FAKE_TOKEN_FOR_DEBUG = "fake_token"; -// public static final String PARAM_URI_TOKEN_INFO = "uriTokenInfo"; -// public static final String PARAM_URI_GETTOKEN = "uriGetToken"; -// public static final String PARAM_URI_LOGIN_REDIRECT = "uriLoginRedirect"; -// public static final String PARAM_CLIENT_ID = "clientId"; -// public static final String PARAM_CLIENT_SECRET = "clientSecret"; -// public static final String PARAM_CALLBACK_URI = "callbackUri"; -// public static final String PARAM_AUDIENCE = "audience"; - + 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; + + // TODO parameterise + // 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 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"; - // or github: + // 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 apiURLBase = "https://api.github.com/"; - -// private String uriTokenRedirect = "/"; - // google - // TODO parameterise - private String clientId = "789182012565-burd24h3bc0im74g2qemi7lnihvfqd02.apps.googleusercontent.com"; - private String clientSecret = "X00v-LfU34U4SfsHqPKMWfQl"; - // github // private String clientId = "7f76b9970d8ac15b30b0"; // private String clientSecret = "9e15f8dd651f0b1896a3a582f17fa82f049fc910"; - // TODO - private String audience = "audience"; - -// private static final String OAUTH2_TOKEN = "org.apache.activemq.jaas.oauth2.token"; -// private static final String OAUTH2_ROLE = "org.apache.activemq.jaas.oauth2.role"; -// private static final String OAUTH2_URL = "org.apache.activemq.jaas.oauth2.oauth2url"; -// private Set<Principal> principals = new HashSet<>(); -// private Subject subject; -// private CallbackHandler callbackHandler; -// private boolean debug; -// private String roleName = "webconsole"; -// private String oauth2URL = uriTokenInfo; -// private boolean loginSucceeded; -// private String userName; -// private boolean commitSuccess; - @Override public boolean isAuthenticated(HttpSession session) { - LOG.info("isAuthenticated 1 "+session+" ... "+this); - Object token = session.getAttribute(SESSION_KEY_ACCESS_TOKEN); + LOG.info("isAuthenticated 1 "+getJettyRequest().getRequestURI()+" "+session+" ... "+this); + 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) { + public boolean authenticate(HttpSession session, String user, String password) throws SecurityProviderDeniedAuthentication { LOG.info("authenticate "+session+" "+user); if (isAuthenticated(session)) { @@ -131,18 +109,23 @@ public class OauthSecurityProvider implements SecurityProvider, SecurityProvider Request request = getJettyRequest(); // Redirection from the authenticator server - String code = request.getParameter(SESSION_KEY_CODE); + String code = request.getParameter(OAUTH_AUTH_CODE_PARAMETER_FROM_USER); // Getting token, if exists, from the current session - String token = (String) request.getSession().getAttribute(SESSION_KEY_ACCESS_TOKEN); + String token = (String) request.getSession().getAttribute(OAUTH_ACCESS_TOKEN_SESSION_KEY); try { if (Strings.isNonBlank(code)) { - return getToken(); - } else if (Strings.isEmpty(token)) { - return redirectLogin(); + 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 { - return validateToken(token); + // 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); @@ -152,7 +135,8 @@ public class OauthSecurityProvider implements SecurityProvider, SecurityProvider @Override public boolean logout(HttpSession session) { LOG.info("logout"); - session.removeAttribute(SESSION_KEY_ACCESS_TOKEN); + session.removeAttribute(OAUTH_ACCESS_TOKEN_SESSION_KEY); + session.removeAttribute(OAUTH_ACCESS_TOKEN_EXPIRY_UTC_KEY); return true; } @@ -161,13 +145,10 @@ public class OauthSecurityProvider implements SecurityProvider, SecurityProvider return false; } - private boolean getToken() throws ClientProtocolException, IOException, ServletException { - Request request = getJettyRequest(); - String code = request.getParameter(SESSION_KEY_CODE); - + 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(SESSION_KEY_CODE, code); + params.put(OAUTH_AUTH_CODE_PARAMETER_FOR_SERVER, code); params.put("client_id", clientId); params.put("client_secret", clientSecret); params.put("redirect_uri", callbackUri); @@ -180,18 +161,22 @@ public class OauthSecurityProvider implements SecurityProvider, SecurityProvider // get the access token from json and request info from Google try { jsonObject = (Map<?,?>) Yamls.parseAll(body).iterator().next(); - logger.info("Parsed '"+body+"' as "+jsonObject); + LOG.info("Parsed '"+body+"' as "+jsonObject); } catch (Exception e) { Exceptions.propagateIfFatal(e); - logger.info("Unable to parse: '"+body+"'"); + LOG.info("Unable to parse: '"+body+"'"); // throw new RuntimeException("Unable to parse json " + body); - return redirectLogin(); + return redirectUserToOauthLoginUi(); } - // Left token and code in session - String accessToken = (String) jsonObject.get(SESSION_KEY_ACCESS_TOKEN); - request.getSession().setAttribute(SESSION_KEY_ACCESS_TOKEN, accessToken); - request.getSession().setAttribute(SESSION_KEY_CODE, code); + // 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); @@ -205,46 +190,40 @@ public class OauthSecurityProvider implements SecurityProvider, SecurityProvider // scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.me, // token_type=Bearer, // id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc5NzhhOTEzNDcyNjFhMjkxYmQ3MWRjYWI0YTQ2NGJlN2QyNzk2NjYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiYXpwIjoiNzg5MTgyMDEyNTY1LWJ1cmQyNGgzYmMwaW03NGcycWVtaTdsbmlodmZxZDAyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiNzg5MTgyMDEyNTY1LWJ1cmQyNGgzYmMwaW03NGcycWVtaTdsbmlodmZxZDAyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTA2MDQyNTE3MjU2MTcxNzYyMTU0IiwiaGQiOiJjbG91ZHNvZnRjb3JwLmNvbSIsImVtYWlsIjoiYWxleC5oZW5ldmVsZEBjbG91ZHN [...] - // TODO how do we get the user ID back? + // 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; } - - @Override - public void postAuthenticate(ContainerRequestContext requestContext) { - Request request = getJettyRequest(); - String token = (String) request.getSession().getAttribute(SESSION_KEY_ACCESS_TOKEN); - LOG.info("TOKEN post authed = "+token); - String user = token; // TODO not right - see above - requestContext.setSecurityContext(new SimpleSecurityContext(user, (role) -> false, request.isSecure(), "brooklyn-oauth")); - } - - private boolean validateToken(String token) throws ClientProtocolException, IOException { - // TODO for debug - if(token.equals(FAKE_TOKEN_FOR_DEBUG)){ - return true; - } - HashMap<String, String> params = new HashMap<String, String>(); - params.put(SESSION_KEY_ACCESS_TOKEN, token); - - String body = post(uriTokenInfo, params); - // System.out.println(body); - Map<?,?> jsonObject = null; - - // get the access token from json and request info from Google - try { - jsonObject = (Map<?,?>) Yamls.parseAll(body).iterator().next(); - logger.info("Parsed '"+body+"' as "+jsonObject); - } catch (Exception e) { - Exceptions.propagateIfFatal(e); - logger.info("Unable to parse: '"+body+"'"); - throw new RuntimeException("Unable to parse json " + body, e); - } - // TODO what's this for? - if (!clientId.equals(jsonObject.get(audience))) { - return redirectLogin(); - } - // if (isTokenExpiredOrNearlySo(...) { ... } + 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; } @@ -286,7 +265,7 @@ public class OauthSecurityProvider implements SecurityProvider, SecurityProvider return body; } - private boolean redirectLogin() throws IOException { + 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") @@ -304,17 +283,12 @@ public class OauthSecurityProvider implements SecurityProvider, SecurityProvider // just for look inside // Collection<String> originalHeaders = response.getHeaderNames(); - Response response = getJettyResponse(); - response.reset(); + 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"); - LOG.info("OAUTH url redirect is: "+oauthUrl.toString()); - - response.sendRedirect(oauthUrl.toString()); - - return false; +// response.addHeader("Access-Control-Request-Method", "GET, POST"); +// response.addHeader("Access-Control-Request-Headers", "origin, x-requested-with"); } private Request getJettyRequest() { @@ -324,10 +298,4 @@ public class OauthSecurityProvider implements SecurityProvider, SecurityProvider .orElse(null); } - private Response getJettyResponse() { - return Optional.ofNullable(HttpConnection.getCurrentConnection()) - .map(HttpConnection::getHttpChannel) - .map(HttpChannel::getResponse) - .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 1a16219..7ceecf0 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 @@ -19,7 +19,7 @@ package org.apache.brooklyn.rest.security.provider; import javax.servlet.http.HttpSession; -import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Response; /** * The SecurityProvider is responsible for doing authentication. @@ -35,12 +35,24 @@ public interface SecurityProvider { * (ie for things like oauth, the framework should not require basic auth if this method returns false) */ public boolean requiresUserPass(); - public boolean authenticate(HttpSession session, String user, String pass); + /** 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(HttpSession session, String user, String pass) throws SecurityProviderDeniedAuthentication; public boolean logout(HttpSession session); - public interface PostAuthenticator { - /** Invoked by framework after successful authentication for principals to be updated. - * (That needs to happen against the container which is not otherwise accessible.) */ - public void postAuthenticate(ContainerRequestContext requestContext); + 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 46a2238..fc80fe8 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.ContextResolver; 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 class ManagementContextProvider implements ContextResolver<ManagementCont 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 class ManagementContextProvider implements ContextResolver<ManagementCont @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 f357222..1f10b4b 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 @@ -108,14 +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.brooklyn.rest.filter.BrooklynSecurityProviderFilter"/> <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/test/java/org/apache/brooklyn/rest/filter/EntitlementContextFilterTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/filter/EntitlementContextFilterTest.java index 3c7748b..7b75428 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 @@ -34,7 +34,7 @@ import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest; import org.apache.cxf.jaxrs.JAXRSServerFactoryBean; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.http.HttpStatus; -import org.eclipse.jetty.server.session.HashSessionManager; +import org.eclipse.jetty.server.session.SessionHandler; import org.testng.annotations.Test; public class EntitlementContextFilterTest extends BrooklynRestResourceTest { @@ -57,13 +57,13 @@ public class EntitlementContextFilterTest extends BrooklynRestResourceTest { props.put(BrooklynWebConfig.PASSWORD_FOR_USER(USER_PASS), USER_PASS); getManagementContext().getScratchpad().put(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE, new ExplicitUsersSecurityProvider(getManagementContext())); - sf.setProvider(new HashSessionManager()); + sf.setProvider(new SessionHandler()); super.configureCXF(sf); } @Override protected void addBrooklynResources() { - addResource(new BrooklynSecurityProviderFilter()); + addResource(new BrooklynSecurityProviderFilterHelper()); addResource(new RequestTaggingRsFilter()); addResource(new EntitlementContextFilter()); addResource(new EntitlementResource()); 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 0e2ba61..4a52a53 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 @@ -49,7 +49,6 @@ 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.eclipse.jetty.server.session.HashSessionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java index d961f7d..33bbfd3 100644 --- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java +++ b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java @@ -26,17 +26,14 @@ import javax.servlet.DispatcherType; import javax.servlet.Filter; import org.apache.brooklyn.rest.apidoc.RestApiResourceScanner; -import org.apache.brooklyn.rest.filter.MyOauthFilter; import org.apache.cxf.BusFactory; import org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet; import org.apache.cxf.transport.common.gzip.GZIPInInterceptor; import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor; -import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import io.swagger.config.ScannerFactory; -import org.eclipse.jetty.webapp.WebAppContext; public class RestApiSetup { @@ -73,18 +70,4 @@ public class RestApiSetup { ScannerFactory.setScanner(new RestApiResourceScanner()); } - public static void installOauthServletFilters(WebAppContext context, Class<MyOauthFilter> myOauthFilterClass) { - FilterHolder fh= context.addFilter(myOauthFilterClass, "/*", EnumSet.allOf(DispatcherType.class)); - setFilterParams(fh); - } - private static void setFilterParams(FilterHolder fh) { - fh.setInitParameter(MyOauthFilter.PARAM_URI_GETTOKEN, "https://accounts.google.com/o/oauth2/token"); - fh.setInitParameter(MyOauthFilter.PARAM_URI_TOKEN_INFO, "https://www.googleapis.com/oauth2/v1/tokeninfo"); - fh.setInitParameter(MyOauthFilter.PARAM_URI_LOGIN_REDIRECT, "/login"); - fh.setInitParameter(MyOauthFilter.PARAM_CLIENT_ID, - "789182012565-burd24h3bc0im74g2qemi7lnihvfqd02.apps.googleusercontent.com"); - fh.setInitParameter(MyOauthFilter.PARAM_CLIENT_SECRET, "X00v-LfU34U4SfsHqPKMWfQl"); - fh.setInitParameter(MyOauthFilter.PARAM_CALLBACK_URI, "http://localhost.io:8080/service/ping"); - fh.setInitParameter(MyOauthFilter.PARAM_AUDIENCE, "audience"); - } } 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 fcdea9d..0000000 --- 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(BrooklynSecurityProviderFilter.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/java/org/apache/brooklyn/rest/filter/MyOauthFilter.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/MyOauthFilter.java deleted file mode 100644 index 4ae2b4a..0000000 --- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/MyOauthFilter.java +++ /dev/null @@ -1,244 +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 java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -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 net.minidev.json.JSONObject; -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.entity.ContentType; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; - -public class MyOauthFilter implements Filter { - - private static final String SESSION_KEY_CODE = "code"; - - private static final String SESSION_KEY_ACCESS_TOKEN = "access_token"; - - public static final String PARAM_URI_TOKEN_INFO = "uriTokenInfo"; - private String uriTokenInfo = ""; - public static final String PARAM_URI_GETTOKEN = "uriGetToken"; - private String uriGetToken = ""; - public static final String PARAM_URI_LOGIN_REDIRECT = "uriLoginRedirect"; - private String uriTokenRedirect = ""; - public static final String PARAM_CLIENT_ID = "clientId"; - private String clientId = ""; - public static final String PARAM_CLIENT_SECRET = "clientSecret"; - private String clientSecret = ""; - public static final String PARAM_CALLBACK_URI = "callbackUri"; - private String callbackUri = ""; - public static final String PARAM_AUDIENCE = "audience"; - private String audience = ""; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - initializateParams(filterConfig); - } - - private void initializateParams(FilterConfig filterConfig) { - Enumeration<String> enums = filterConfig.getInitParameterNames(); - - while (enums.hasMoreElements()) { - String paramKey = enums.nextElement(); - String paramValue = filterConfig.getInitParameter(paramKey); - System.out.println(paramKey + ":" + paramValue); - switch (paramKey) { - case PARAM_URI_TOKEN_INFO: - uriTokenInfo = paramValue; - break; - case PARAM_URI_GETTOKEN: - uriGetToken = paramValue; - break; - case PARAM_URI_LOGIN_REDIRECT: - uriTokenRedirect = paramValue; - break; - case PARAM_CLIENT_ID: - clientId = paramValue; - break; - case PARAM_CLIENT_SECRET: - clientSecret = paramValue; - break; - case PARAM_CALLBACK_URI: - callbackUri = paramValue; - break; - case PARAM_AUDIENCE: - audience = paramValue; - break; - default: - System.out.println("Ignored param: " + paramKey + ":" + paramValue); - } - } - } - - @Override - public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) - throws IOException, ServletException { - HttpServletRequest request = (HttpServletRequest) req; - // Redirection from the authenticator server - String code = req.getParameter(SESSION_KEY_CODE); - - // Getting token, if exists, from the current session - String token = (String) request.getSession().getAttribute(SESSION_KEY_ACCESS_TOKEN); - - boolean continueFilterProcessing; - if (code != null && !"".equals(code)) { // in brooklyn, have - // Strings.isNonBlank(code) - continueFilterProcessing = getToken(req, resp, chain); - } else if (token == null || "".equals(token)) { // isBlank - continueFilterProcessing = redirectLogin(resp); - } else { - continueFilterProcessing = validateToken(token, resp); - } - if (continueFilterProcessing) { - chain.doFilter(req, resp); - } - } - - private boolean validateToken(String token, ServletResponse resp) throws ClientProtocolException, IOException { - // System.out.println("########################### Validating token - // ###########################"); - HashMap<String, String> params = new HashMap<String, String>(); - params.put(SESSION_KEY_ACCESS_TOKEN, token); - - String body = post(uriTokenInfo, params); - // System.out.println(body); - JSONObject jsonObject = null; - - // get the access token from json and request info from Google - try { - jsonObject = (JSONObject) new JSONParser().parse(body); - } catch (ParseException e) { - throw new RuntimeException("Unable to parse json " + body); - } - - if (!clientId.equals(jsonObject.get(audience))) { - return redirectLogin(resp); - } - // if (isTokenExpiredOrNearlySo(...) { ... } - return true; - } - - private boolean getToken(ServletRequest req, ServletResponse resp, FilterChain chain) - throws ClientProtocolException, IOException, ServletException { - String code = req.getParameter(SESSION_KEY_CODE); - - // get the access token by post to Google - HashMap<String, String> params = new HashMap<String, String>(); - params.put(SESSION_KEY_CODE, 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); - - JSONObject jsonObject = null; - - // get the access token from json and request info from Google - try { - jsonObject = (JSONObject) new JSONParser().parse(body); - } catch (ParseException e) { - // throw new RuntimeException("Unable to parse json " + body); - return redirectLogin(resp); - } - - // Left token and code in session - String accessToken = (String) jsonObject.get(SESSION_KEY_ACCESS_TOKEN); - HttpServletRequest request = (HttpServletRequest) req; - request.getSession().setAttribute(SESSION_KEY_ACCESS_TOKEN, accessToken); - request.getSession().setAttribute(SESSION_KEY_CODE, code); - - // resp.getWriter().println(json); - return true; - } - - // 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 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 request and checks response code for 200 - private String execute(HttpRequestBase request) throws ClientProtocolException, IOException { - 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 redirectLogin(ServletResponse response) throws IOException { - HttpServletResponse res = (HttpServletResponse) response; - res.setContentType(ContentType.APPLICATION_XML.toString()); - res.sendRedirect(uriTokenRedirect); - return false; - } - - @Override - public void destroy() { - // TODO Auto-generated method stub - } - -} 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 eaa20f4..ec117e0 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,6 +38,7 @@ import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; 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.BrooklynSecurityProviderFilterHelper; import org.apache.brooklyn.rest.filter.CorsImplSupplierFilter; import org.apache.brooklyn.rest.filter.CsrfTokenFilter; import org.apache.brooklyn.rest.filter.EntitlementContextFilter; @@ -46,8 +47,6 @@ 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.security.provider.AnyoneSecurityProvider; import org.apache.brooklyn.rest.security.provider.SecurityProvider; import org.apache.brooklyn.rest.util.ManagementContextProvider; @@ -58,7 +57,6 @@ import org.apache.brooklyn.util.exceptions.Exceptions; 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; @@ -281,7 +279,7 @@ public class BrooklynRestApiLauncher { 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 +294,8 @@ public class BrooklynRestApiLauncher { } // 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() {
