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 5418709ae82ab05acb6f9e4695a1814558c568c4 Author: Juan Cabrerizo <[email protected]> AuthorDate: Thu Nov 29 16:20:19 2018 +0000 GoogleOauthLoginModule, doesn't want to redirect --- .../brooklyn/rest/filter/GoogleOauthFilter.java | 17 +- .../rest/security/jaas/GoogleOauthLoginModule.java | 339 +++++++++++++++++++++ .../provider/GoogleOauthSecurityProvider.java | 5 +- .../main/resources/OSGI-INF/blueprint/service.xml | 4 +- rest/rest-resources/src/main/resources/jaas.conf | 2 +- .../src/main/resources/web-security.xml | 2 +- 6 files changed, 354 insertions(+), 15 deletions(-) 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 index 033f035..3a85db6 100644 --- 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 @@ -54,7 +54,7 @@ import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; @Provider -@Priority(100) +@Priority(1) public class GoogleOauthFilter implements Filter { public static final String SESSION_KEY_CODE = "code"; @@ -62,19 +62,20 @@ public class GoogleOauthFilter implements Filter { public 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 = ""; + + 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 { 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 new file mode 100644 index 0000000..2e497c6 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/GoogleOauthLoginModule.java @@ -0,0 +1,339 @@ +/* + * 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 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.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; +import java.util.*; +import java.io.IOException; +import java.security.Principal; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.servlet.ServletException; + + +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"; +// 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"; + + 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) { + 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.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); + + try { + if (code != null && !"".equals(code)) { // in brooklyn, have + // Strings.isNonBlank(code) + eligible = getToken(); + } else if (token == null || "".equals(token)) { // isBlank + 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); + + 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(); + } + + // 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 + // ###########################"); + 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(); + } + // 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 boolean redirectLogin() throws IOException { + String state="state"; + StringBuilder oauthUrl = new StringBuilder().append("https://accounts.google.com/o/oauth2/auth") + .append("?client_id=").append(clientId) // the client id from the api console registration + .append("&response_type=code").append("&scope=openid%20email") // scope is the api permissions we + // are requesting + .append("&redirect_uri=").append(callbackUri) // the servlet that google redirects to after + // authorization + .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 + logger.debug(oauthUrl.toString()); + response.addHeader("Access-Control-Allow-Origin","accounts.google.com"); + 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/provider/GoogleOauthSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/GoogleOauthSecurityProvider.java index 809c661..21f022e 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/GoogleOauthSecurityProvider.java @@ -40,14 +40,13 @@ public class GoogleOauthSecurityProvider implements SecurityProvider { @Override public boolean authenticate(HttpSession session, String user, String password) { LOG.info("authenticate"); - // - return false; + return true; } @Override public boolean logout(HttpSession session) { LOG.info("logout"); - session.removeAttribute("xx"); + session.removeAttribute(GoogleOauthFilter.SESSION_KEY_ACCESS_TOKEN); return true; } } 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 f329d77..bb6e8f8 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 @@ -60,7 +60,8 @@ limitations under the License. interface="org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal"/> <jaas:config name="webconsole"> - <jaas:module className="org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule" flags="required"/> + <jaas:module className="org.apache.brooklyn.rest.security.jaas.GoogleOauthLoginModule" flags="required"/> + <!--<jaas:module className="org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule" flags="required"/>--> </jaas:config> <reference id="shutdownHandler" interface="org.apache.brooklyn.core.mgmt.ShutdownHandler"/> @@ -133,7 +134,6 @@ limitations under the License. <bean class="org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter"/> <bean class="org.apache.brooklyn.rest.filter.EntitlementContextFilter"/> <bean class="org.apache.brooklyn.rest.filter.LoggingResourceFilter"/> - <bean class="org.apache.brooklyn.rest.filter.GoogleOauthFilter"/> <bean class="io.swagger.jaxrs.listing.SwaggerSerializers"/> <bean class="org.apache.brooklyn.rest.util.ShutdownHandlerProvider"> <argument ref="shutdownHandler"/> diff --git a/rest/rest-resources/src/main/resources/jaas.conf b/rest/rest-resources/src/main/resources/jaas.conf index bb18334..45dc90a 100644 --- a/rest/rest-resources/src/main/resources/jaas.conf +++ b/rest/rest-resources/src/main/resources/jaas.conf @@ -17,5 +17,5 @@ * under the License. */ webconsole { - org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule required; + org.apache.brooklyn.rest.security.jaas.GoogleOauthLoginModule required; }; diff --git a/rest/rest-server/src/main/resources/web-security.xml b/rest/rest-server/src/main/resources/web-security.xml index 2311458..a03c448 100644 --- a/rest/rest-server/src/main/resources/web-security.xml +++ b/rest/rest-server/src/main/resources/web-security.xml @@ -40,7 +40,7 @@ </security-constraint> <login-config> - <auth-method>BASIC</auth-method> + <!--<auth-method>BASIC</auth-method>--> <realm-name>webconsole</realm-name> </login-config>
