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 2b0cd8d35f1a7a9139a131e8a1532661f2001092 Author: Alex Heneveld <[email protected]> AuthorDate: Fri Jan 4 17:48:41 2019 +0000 clean up removing jaas stuff to support oauth still needs configuration and testing, but getting there --- .../filter/BrooklynSecurityProviderFilter.java | 178 +++++---- .../brooklyn/rest/filter/GoogleOauthFilter.java | 266 ------------- .../brooklyn/rest/resources/LogoutResource.java | 8 - .../rest/security/jaas/BrooklynLoginModule.java | 423 --------------------- .../rest/security/jaas/GoogleOauthLoginModule.java | 391 ------------------- .../brooklyn/rest/security/jaas/JaasUtils.java | 48 --- .../security/jaas/ManagementContextHolder.java | 36 -- .../security/jaas/SecurityProviderHttpSession.java | 120 ------ .../provider/DelegatingSecurityProvider.java | 53 ++- ...ityProvider.java => OauthSecurityProvider.java} | 46 ++- .../rest/security/provider/SecurityProvider.java | 6 + .../main/resources/OSGI-INF/blueprint/service.xml | 4 - .../rest/filter/EntitlementContextFilterTest.java | 12 +- .../security/jaas/BrooklynLoginModuleTest.java | 195 ---------- .../rest/security/jaas/TestCallbackHandler.java | 50 --- .../brooklyn/rest/testing/BrooklynRestApiTest.java | 4 +- .../rest/testing/BrooklynRestResourceTest.java | 16 +- 17 files changed, 218 insertions(+), 1638 deletions(-) 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 index 264a575..4f960f6 100644 --- 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 @@ -19,23 +19,28 @@ 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.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; -import javax.ws.rs.container.ContainerResponseContext; -import javax.ws.rs.container.ContainerResponseFilter; 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.jaas.BrooklynLoginModule; +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; @@ -48,13 +53,22 @@ import org.slf4j.LoggerFactory; */ @Provider @Priority(100) -public class BrooklynSecurityProviderFilter implements ContainerRequestFilter, ContainerResponseFilter { +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; - HttpServletResponse webResponse; - @Context private ContextResolver<ManagementContext> mgmtC; @@ -62,6 +76,40 @@ public class BrooklynSecurityProviderFilter implements ContainerRequestFilter, C 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(); @@ -71,41 +119,61 @@ public class BrooklynSecurityProviderFilter implements ContainerRequestFilter, C return; } - String user=null, pass=null; - webResponse.setHeader("WWW-Authenticate", "Basic realm=\"brooklyn\""); - webResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); - throw new RuntimeException("Authentication required."); -// if (provider.requiresUserPass()) { -// // TODO get basic auth -// -// } -// -// if (provider.authenticate(session, user, pass)) { -// -// } else { -// throw new RuntimeException("Authentication failed."); -// } - } + 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; + } - @Override - public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { - // nothing needs done on the response + abort(requestContext, "Authentication failed"); + return; } - protected SecurityProvider getProvider() { - // TODO - return null; + 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()); } - /** - * 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 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>(); // @@ -173,42 +241,6 @@ public class BrooklynSecurityProviderFilter implements ContainerRequestFilter, C // } // // 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 {}", diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/GoogleOauthFilter.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/GoogleOauthFilter.java deleted file mode 100644 index ab15f1a..0000000 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/GoogleOauthFilter.java +++ /dev/null @@ -1,266 +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.annotation.Priority; -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.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerRequestFilter; -import javax.ws.rs.container.ContainerResponseContext; -import javax.ws.rs.container.ContainerResponseFilter; -import javax.ws.rs.ext.Provider; - -import org.apache.brooklyn.util.exceptions.Exceptions; -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.entity.ContentType; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Provider -@Priority(1) -public class GoogleOauthFilter implements ContainerRequestFilter { - - private static final Logger log = LoggerFactory.getLogger(GoogleOauthFilter.class); - - public static final String SESSION_KEY_CODE = "code"; - - public static final String SESSION_KEY_ACCESS_TOKEN = "access_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 String uriGetToken = "https://accounts.google.com/o/oauth2/token"; - private String uriTokenInfo = "https://www.googleapis.com/oauth2/v1/tokeninfo"; - private String uriTokenRedirect = "/"; - private String clientId = "789182012565-burd24h3bc0im74g2qemi7lnihvfqd02.apps.googleusercontent.com"; - private String clientSecret = "X00v-LfU34U4SfsHqPKMWfQl"; - private String callbackUri = "http://localhost.io:8081/"; - private String audience = "audience"; - -// @Override -// public void init(FilterConfig filterConfig) throws ServletException { -// log.info("GOOGLE FILTER 1"); -// 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 { -// log.info("GOOGLE FILTER 2"); -// 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 { -// log.info("GOOGLE FILTER 3"); -// // 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); -// 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))) { -// 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); -// -// 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+"'"); -// 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 filter(ContainerRequestContext requestContext) throws IOException { - log.info("GOOGLE FILTER 2"); - // TODO Auto-generated method stub - - } - -} 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 e3329d2..5ce65ba 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,10 @@ import javax.ws.rs.core.UriInfo; 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.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 +44,6 @@ public class LogoutResource extends AbstractBrooklynRestResource implements Logo if (ctx==null) { return Response.status(Status.BAD_REQUEST) .entity("No user logged in") - .header(HttpHeaders.WWW_AUTHENTICATE, BASIC_REALM_WEBCONSOLE) .build(); } @@ -64,7 +58,6 @@ public class LogoutResource extends AbstractBrooklynRestResource implements Logo @Override public Response unAuthorize() { return Response.status(Status.UNAUTHORIZED) - .header(HttpHeaders.WWW_AUTHENTICATE, BASIC_REALM_WEBCONSOLE) .build(); } @@ -77,7 +70,6 @@ public class LogoutResource extends AbstractBrooklynRestResource implements Logo doLogout(); return Response.status(Status.UNAUTHORIZED) - .header(HttpHeaders.WWW_AUTHENTICATE, BASIC_REALM_WEBCONSOLE) .build(); } else { return Response.temporaryRedirect(uri.getAbsolutePathBuilder().replacePath("/").build()).build(); 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 9374fbe..0000000 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/BrooklynLoginModule.java +++ /dev/null @@ -1,423 +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> - * We have also supported configuring this as options in OSGi; - * this is now deprecated, but see {@link #initProviderFromOptions(StringConfigMap)} for more info. - */ -// 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) { - try { - this.subject = subject; - this.callbackHandler = callbackHandler; - this.options = options; - - this.bundleContext = (BundleContext) options.get(BundleContext.class.getName()); - - loginSuccess = false; - commitSuccess = false; - - initProvider(); - - } catch (Exception e) { - log.warn("Unable to initialize BrooklynLoginModule: "+e, e); - Exceptions.propagateIfFatal(e); - } - } - - private void initProvider() { - // use existing (shared) provider if there is one, for speed - // (note this login module class gets a different instance on each request; caching the provider is a big efficiency gain) - StringConfigMap brooklynProperties = getManagementContext().getConfig(); - provider = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE); - if (provider != null) return; - provider = getManagementContext().getScratchpad().get(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE); - if (provider != null) return; - - initProviderFromOptions(brooklynProperties); - if (provider==null) { - // no osgi options set, so use the standard properties-based one (usual path) - provider = createDefaultSecurityProvider(getManagementContext()); - } - - log.debug("Using security provider " + provider); - } - - /** - * We have since switching to OSGi also allowed the provider to be specified as an option in the - * OSGi blueprint. This has never been used AFAIK in the real world but it was the only way to specify a bundle. - * Note that only implementations visible from {@literal brooklyn-rest-server} are usable by default. - * We now support specifying a bundle for the delegate, but this is being left in as deprecated. - * To use this <b>deprecated</b> configuration with 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> - * @deprecated since 2019-01, use the brooklyn system properties - * {@link BrooklynWebConfig#SECURITY_PROVIDER_CLASSNAME}, - * {@link BrooklynWebConfig#SECURITY_PROVIDER_BUNDLE}, and - * {@link BrooklynWebConfig#SECURITY_PROVIDER_BUNDLE_VERSION}, - */ - protected void initProviderFromOptions(StringConfigMap brooklynProperties) { - // this uses *options* to determine the security provider to load - // (not sure this is ever used) - 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 (symbolicName != null) { - if (className == null) { - className = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME); - } - if (className != null) { - provider = loadProviderFromBundle(getManagementContext(), bundleContext, symbolicName, version, className); - } - } - } - - 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("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; - } - - @Override - public boolean login() throws LoginException { - try { - log.info("ALEX BLM login - "+callbackHandler+" "+this+" "+provider); - String user=null, password=null; - - if (provider.requiresUserPass()) { - 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"); - } - user = cbName.getName(); - password = new String(cbPassword.getPassword()); - } - - - Request req = getJettyRequest(); - if (req != null) { - providerSession = req.getSession(false); - } - log.info("GOT SESSION - "+providerSession); - if (providerSession == null) { - providerSession = new SecurityProviderHttpSession(); - } - 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; - } catch (LoginException e) { - throw e; - } catch (Exception e) { - log.warn("Unexpected error during login: "+e, e); - throw e; - } - } - - @Override - public boolean commit() throws LoginException { - log.info("ALEX BLM BR LOGIN - COMMIT"); - if (loginSuccess && principals!=null && !principals.isEmpty()) { - // for oauth principals aren't set currently; they don't seem to be needed - - 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 { - log.info("ALEX BLM abort"); - 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 (principals==null || principals.isEmpty()) return; - 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/GoogleOauthLoginModule.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/GoogleOauthLoginModule.java deleted file mode 100644 index bd1493f..0000000 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/GoogleOauthLoginModule.java +++ /dev/null @@ -1,391 +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 java.security.Principal; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; -import java.util.Set; - -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.UnsupportedCallbackException; -import javax.security.auth.login.LoginException; -import javax.security.auth.spi.LoginModule; -import javax.servlet.ServletException; - -import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.text.Strings; -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.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; - - -public class GoogleOauthLoginModule implements LoginModule { - private static final Logger logger = LoggerFactory.getLogger(BrooklynLoginModule.class); - private static final String SESSION_KEY_ACCESS_TOKEN = "access_token"; - private static final String SESSION_KEY_CODE = "code"; - private static final String TOKEN = "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 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"; - - // or github: -// 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 - private String clientId = "789182012565-burd24h3bc0im74g2qemi7lnihvfqd02.apps.googleusercontent.com"; - private String clientSecret = "X00v-LfU34U4SfsHqPKMWfQl"; - // github -// private String clientId = "7f76b9970d8ac15b30b0"; -// private String clientSecret = "9e15f8dd651f0b1896a3a582f17fa82f049fc910"; - private String callbackUri = "http://localhost.io:8081/"; - 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; - - private final Request request=getJettyRequest(); - private final Response response=getJettyResponse(); - - @Override - public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { - logger.info("ALEX LOGIN MODULE - INIT"); - this.subject = subject; - this.callbackHandler = callbackHandler; - - loginSucceeded = false; - commitSuccess = false; - - debug = !"false".equalsIgnoreCase((String) options.get("debug")); - - if (debug) { - logger.debug(">>>>>>>>>>>>>>>>Initialized debug=" + debug + " guestGroup=" + roleName + " url=" - + oauth2URL); - } - - } - - @Override - public boolean login() throws LoginException { - loginSucceeded = true; - - Callback[] callbacks = new Callback[1]; - callbacks[0] = new NameCallback("User name"); - - try { - callbackHandler.handle(callbacks); - } catch (IOException | UnsupportedCallbackException e) { - throw (LoginException) new LoginException().initCause(e); - } - - userName = ((NameCallback) callbacks[0]).getName(); - - if (null == userName) { - loginSucceeded = false; - } - - String newUrl = oauth2URL + userName; - logger.info("ALEX LOGIN MODULE - LOGIN "+userName+" / "+newUrl); - logger.debug("THis is the URL: " + newUrl); - - boolean eligible=false; - - // Redirection from the authenticator server - String code = request.getParameter(SESSION_KEY_CODE); - - // Getting token, if exists, from the current session - String token = (String) request.getSession().getAttribute(SESSION_KEY_ACCESS_TOKEN); -// token=TOKEN; - try { - if (Strings.isNonBlank(code)) { - eligible = getToken(); - } else if (Strings.isEmpty(token)) { - eligible = redirectLogin(); - } else { - eligible = validateToken(token); - } - } catch (IOException e) { - e.printStackTrace(); - } catch (ServletException e) { - e.printStackTrace(); - } - - if (eligible) { - principals.add(new BrooklynLoginModule.UserPrincipal(userName)); - principals.add(new BrooklynLoginModule.RolePrincipal(roleName)); - } else { - loginSucceeded = false; - } - - if (debug) { - logger.debug("Token login " + loginSucceeded); - } - return loginSucceeded; - } - - @Override - public boolean commit() throws LoginException { - if (loginSucceeded) { - if (subject.isReadOnly()) { - throw new LoginException("Can't commit read-only subject"); - } - subject.getPrincipals().addAll(principals); - } - - commitSuccess = true; - return loginSucceeded; - } - - @Override - public boolean abort() throws LoginException { - if (loginSucceeded && commitSuccess) { - removePrincipal(); - } - clear(); - return loginSucceeded; - } - - @Override - public boolean logout() throws LoginException { - if (request != null) { -// logger.info("REST logging {} out",providerSession.getAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE)); -// provider.logout(req.getSession()); -// req.getSession().removeAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE); - } else { - logger.error("Request object not available for logout"); - } - - removePrincipal(); - clear(); - return true; - } - private boolean getToken() - throws ClientProtocolException, IOException, ServletException { - String code = request.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); - - 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); - return redirectLogin(); - } - - // 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); - - // resp.getWriter().println(json); - return true; - } - private boolean validateToken(String token) throws ClientProtocolException, IOException { - // System.out.println("########################### Validating token - // ###########################"); - - //for debug - if(token.equals(TOKEN)){ - 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); - } - - if (!clientId.equals(jsonObject.get(audience))) { - return redirectLogin(); - } - // if (isTokenExpiredOrNearlySo(...) { ... } - return true; - } - - // 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 { - 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 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 static String createRandomHexString(int length){ - Random random = new Random(); - StringBuilder sb = new StringBuilder(); - while (sb.length() < length) { - sb.append(Integer.toHexString(random.nextInt())); - } - return sb.toString(); - } - - private boolean redirectLogin() throws IOException { - String state=createRandomHexString(16); //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(); - - response.reset(); -// 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"); - logger.debug(oauthUrl.toString()); - response.sendRedirect(oauthUrl.toString()); - - return false; - - } - - private Request getJettyRequest() { - return Optional.ofNullable(HttpConnection.getCurrentConnection()) - .map(HttpConnection::getHttpChannel) - .map(HttpChannel::getRequest) - .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/jaas/JaasUtils.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/JaasUtils.java deleted file mode 100644 index 94aba5d..0000000 --- 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 84704f1..0000000 --- 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 98f1e6d..0000000 --- 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/DelegatingSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java index d5bb50b..664c5c1 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 @@ package org.apache.brooklyn.rest.security.provider; 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; @@ -29,8 +31,9 @@ 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.rest.security.jaas.BrooklynLoginModule; import org.apache.brooklyn.util.core.ClassLoaderUtils; +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; @@ -92,7 +95,7 @@ public class DelegatingSecurityProvider implements SecurityProvider { String bundleVersion = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_BUNDLE_VERSION); log.info("REST using security provider " + className + " from " + bundle+":"+bundleVersion); BundleContext bundleContext = ((ManagementContextInternal)mgmt).getOsgiManager().get().getFramework().getBundleContext(); - delegate = BrooklynLoginModule.loadProviderFromBundle(mgmt, bundleContext, bundle, bundleVersion, className); + delegate = loadProviderFromBundle(mgmt, bundleContext, bundle, bundleVersion, className); } else { log.info("REST using security provider " + className); ClassLoaderUtils clu = new ClassLoaderUtils(this, mgmt); @@ -111,6 +114,52 @@ public class DelegatingSecurityProvider implements SecurityProvider { 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("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 { diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/GoogleOauthSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/OauthSecurityProvider.java similarity index 79% rename from rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/GoogleOauthSecurityProvider.java rename to rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/OauthSecurityProvider.java index 89e4844..b3975f1 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/GoogleOauthSecurityProvider.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/OauthSecurityProvider.java @@ -27,9 +27,9 @@ import java.util.Optional; import javax.servlet.ServletException; import javax.servlet.http.HttpSession; +import javax.ws.rs.container.ContainerRequestContext; -import org.apache.brooklyn.rest.filter.GoogleOauthFilter; -import org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule; +import org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilter.SimpleSecurityContext; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.text.Identifiers; import org.apache.brooklyn.util.text.Strings; @@ -53,12 +53,15 @@ import org.eclipse.jetty.server.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** provider who allows everyone */ -public class GoogleOauthSecurityProvider implements SecurityProvider { +/** 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 static final Logger LOG = LoggerFactory.getLogger(GoogleOauthSecurityProvider.class); + public static final Logger LOG = LoggerFactory.getLogger(OauthSecurityProvider.class); - private static final Logger logger = LoggerFactory.getLogger(BrooklynLoginModule.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"; @@ -70,6 +73,10 @@ public class GoogleOauthSecurityProvider implements SecurityProvider { // public static final String PARAM_CALLBACK_URI = "callbackUri"; // public static final String PARAM_AUDIENCE = "audience"; + // tempting to use getJettyRequest().getRequestURL().toString(); + // but some oauth providers require this to be declared + private String callbackUri = "http://localhost.io:8081/"; + 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"; @@ -109,7 +116,7 @@ public class GoogleOauthSecurityProvider implements SecurityProvider { @Override public boolean isAuthenticated(HttpSession session) { LOG.info("isAuthenticated 1 "+session+" ... "+this); - Object token = session.getAttribute(GoogleOauthFilter.SESSION_KEY_ACCESS_TOKEN); + Object token = session.getAttribute(SESSION_KEY_ACCESS_TOKEN); // TODO is it valid? return token!=null; } @@ -145,7 +152,7 @@ public class GoogleOauthSecurityProvider implements SecurityProvider { @Override public boolean logout(HttpSession session) { LOG.info("logout"); - session.removeAttribute(GoogleOauthFilter.SESSION_KEY_ACCESS_TOKEN); + session.removeAttribute(SESSION_KEY_ACCESS_TOKEN); return true; } @@ -157,7 +164,6 @@ public class GoogleOauthSecurityProvider implements SecurityProvider { private boolean getToken() throws ClientProtocolException, IOException, ServletException { Request request = getJettyRequest(); String code = request.getParameter(SESSION_KEY_CODE); - String callbackUri = request.getRequestURL().toString(); // get the access token by post to Google HashMap<String, String> params = new HashMap<String, String>(); @@ -189,10 +195,29 @@ public class GoogleOauthSecurityProvider implements SecurityProvider { // TODO is it valid? LOG.debug("Got token/code "+accessToken+"/"+code+" from "+jsonObject); - // resp.getWriter().println(json); + // 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.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiYXpwIjoiNzg5MTgyMDEyNTY1LWJ1cmQyNGgzYmMwaW03NGcycWVtaTdsbmlodmZxZDAyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiNzg5MTgyMDEyNTY1LWJ1cmQyNGgzYmMwaW03NGcycWVtaTdsbmlodmZxZDAyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTA2MDQyNTE3MjU2MTcxNzYyMTU0IiwiaGQiOiJjbG91ZHNvZnRjb3JwLmNvbSIsImVtYWlsIjoiYWxleC5oZW5ldmVsZEBjbG91ZHN [...] + // TODO how do we get the user ID back? 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)){ @@ -263,7 +288,6 @@ public class GoogleOauthSecurityProvider implements SecurityProvider { private boolean redirectLogin() throws IOException { String state=Identifiers.makeRandomId(12); //should be stored in session - String callbackUri = getJettyRequest().getRequestURL().toString(); StringBuilder oauthUrl = new StringBuilder().append(uriAuthorize) .append("?response_type=").append("code") .append("&client_id=").append(clientId) // the client id from the api console registration 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 42575ff..1a16219 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,6 +19,7 @@ package org.apache.brooklyn.rest.security.provider; import javax.servlet.http.HttpSession; +import javax.ws.rs.container.ContainerRequestContext; /** * The SecurityProvider is responsible for doing authentication. @@ -37,4 +38,9 @@ public interface SecurityProvider { public boolean authenticate(HttpSession session, String user, String pass); 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); + } } 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 dc40b44..f357222 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 @@ -61,10 +61,6 @@ limitations under the License. <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"/> 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 0d37cc8..3c7748b 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.internal.BrooklynProperties; 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.HashSessionManager; import org.testng.annotations.Test; public class EntitlementContextFilterTest extends BrooklynRestResourceTest { @@ -58,18 +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()); super.configureCXF(sf); - - JaasUtils.init(getManagementContext()); - - JAASLoginInterceptor jaas = new JAASLoginInterceptor(); - jaas.setContextName("webconsole"); - sf.getInInterceptors().add(jaas); - } @Override protected void addBrooklynResources() { + addResource(new BrooklynSecurityProviderFilter()); 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 f33e807..0000000 --- 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 4854196..0000000 --- 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 54c9384..de9199e 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 @@ public abstract class BrooklynRestApiTest { 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 183f3de..0e2ba61 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 @@ -47,6 +47,9 @@ 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.eclipse.jetty.server.session.HashSessionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; @@ -58,7 +61,8 @@ public abstract class BrooklynRestResourceTest extends BrooklynRestApiTest { 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,6 +93,12 @@ public abstract class BrooklynRestResourceTest extends BrooklynRestApiTest { protected synchronized void startServer() throws Exception { if (server == null) { setUpResources(); + + // 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); if (clientProviders == null) { clientProviders = sf.getProviders(); @@ -115,6 +125,10 @@ public abstract class BrooklynRestResourceTest extends BrooklynRestApiTest { server.destroy(); server = null; } + if (serverEngine!=null) { + serverEngine.shutdown(); + serverEngine = null; + } }
