From 19500d5c7ea92caf477f9fdf6cf07791b6666825 Mon Sep 17 00:00:00 2001 From: Thomas Grant Date: Wed, 3 May 2017 11:41:01 -0400 Subject: [PATCH 1/5] ZEPPELIN-2268. Adding png and jpg support for helium module imports. --- zeppelin-zengine/src/main/resources/helium/webpack.config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zeppelin-zengine/src/main/resources/helium/webpack.config.js b/zeppelin-zengine/src/main/resources/helium/webpack.config.js index 91d926115ff..e897a162e63 100644 --- a/zeppelin-zengine/src/main/resources/helium/webpack.config.js +++ b/zeppelin-zengine/src/main/resources/helium/webpack.config.js @@ -43,6 +43,8 @@ module.exports = { { test: /\.eot(\?\S*)?$/, loader: 'url-loader', }, { test: /\.ttf(\?\S*)?$/, loader: 'url-loader', }, { test: /\.svg(\?\S*)?$/, loader: 'url-loader', }, { + test: /\.png(\?\S*)?$/, loader: 'url-loader', }, { + test: /\.jpg(\?\S*)?$/, loader: 'url-loader', }, { test: /\.json$/, loader: 'json-loader' }, ], } } From 23f6caa5bccefaac47a45227620d006ea489550f Mon Sep 17 00:00:00 2001 From: Thomas Grant Date: Wed, 7 Jun 2017 12:40:17 -0400 Subject: [PATCH 2/5] Using UserLookup interface to query realms for users and roles. --- .../realm/ActiveDirectoryGroupRealm.java | 10 + .../org/apache/zeppelin/realm/JdbcRealm.java | 88 ++++++ .../apache/zeppelin/realm/LdapGroupRealm.java | 48 +++- .../org/apache/zeppelin/realm/LdapRealm.java | 72 ++++- .../org/apache/zeppelin/realm/PamRealm.java | 6 +- .../org/apache/zeppelin/realm/UserLookup.java | 30 ++ .../org/apache/zeppelin/rest/GetUserList.java | 257 ------------------ .../apache/zeppelin/rest/LoginRestApi.java | 4 +- .../apache/zeppelin/rest/NotebookRestApi.java | 9 +- .../apache/zeppelin/rest/SecurityRestApi.java | 83 +----- .../apache/zeppelin/utils/SecurityUtils.java | 200 ++++++++++++-- 11 files changed, 438 insertions(+), 369 deletions(-) create mode 100644 zeppelin-server/src/main/java/org/apache/zeppelin/realm/JdbcRealm.java create mode 100644 zeppelin-server/src/main/java/org/apache/zeppelin/realm/UserLookup.java delete mode 100644 zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ActiveDirectoryGroupRealm.java b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ActiveDirectoryGroupRealm.java index d40a64378da..ab729b0da9f 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ActiveDirectoryGroupRealm.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ActiveDirectoryGroupRealm.java @@ -376,5 +376,15 @@ protected Collection getRoleNamesForGroups(Collection groupNames return roleNames; } + public List lookupUsers(String searchText) { + List userList = new ArrayList<>(); + try { + LdapContext ctx = getLdapContextFactory().getSystemLdapContext(); + userList = searchForUserName(searchText, ctx); + } catch (Exception e) { + log.error("Error retrieving User list from ActiveDirectory Realm", e); + } + return userList; + } } diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/JdbcRealm.java b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/JdbcRealm.java new file mode 100644 index 00000000000..30956e635aa --- /dev/null +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/JdbcRealm.java @@ -0,0 +1,88 @@ +package org.apache.zeppelin.realm; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import javax.sql.DataSource; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.shiro.util.JdbcUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + */ +public class JdbcRealm extends org.apache.shiro.realm.jdbc.JdbcRealm implements UserLookup { + + private static final Logger LOG = LoggerFactory.getLogger(JdbcRealm.class); + + + @Override + public Collection lookupRoles(String query) { + return Collections.EMPTY_SET; + } + + /** + * function to extract users from JDBCs + */ + @Override + public List lookupUsers(String searchString) { + List userlist = new ArrayList<>(); + PreparedStatement ps = null; + ResultSet rs = null; + DataSource dataSource = null; + String authQuery = ""; + String retval[]; + String tablename = ""; + String username = ""; + String userquery = ""; + try { + dataSource = (DataSource) FieldUtils.readField(this, "dataSource", true); + authQuery = (String) FieldUtils.readField(this, "DEFAULT_AUTHENTICATION_QUERY", true); + LOG.info(authQuery); + String authQueryLowerCase = authQuery.toLowerCase(); + retval = authQueryLowerCase.split("from", 2); + if (retval.length >= 2) { + retval = retval[1].split("with|where", 2); + tablename = retval[0]; + retval = retval[1].split("where", 2); + if (retval.length >= 2) + retval = retval[1].split("=", 2); + else + retval = retval[0].split("=", 2); + username = retval[0]; + } + + if (StringUtils.isBlank(username) || StringUtils.isBlank(tablename)) { + return userlist; + } + + userquery = "select " + username + " from " + tablename; + + } catch (IllegalAccessException e) { + LOG.error("Error while accessing dataSource for JDBC Realm", e); + return null; + } + + try { + Connection con = dataSource.getConnection(); + ps = con.prepareStatement(userquery); + rs = ps.executeQuery(); + while (rs.next()) { + userlist.add(rs.getString(1).trim()); + } + } catch (Exception e) { + LOG.error("Error retrieving User list from JDBC Realm", e); + } finally { + JdbcUtils.closeResultSet(rs); + JdbcUtils.closeStatement(ps); + } + return userlist; + } + +} diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapGroupRealm.java b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapGroupRealm.java index 4133ce055e3..c547d452809 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapGroupRealm.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapGroupRealm.java @@ -16,6 +16,9 @@ */ package org.apache.zeppelin.realm; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.ldap.JndiLdapRealm; @@ -33,13 +36,16 @@ import javax.naming.ldap.LdapContext; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; +import org.apache.shiro.realm.ldap.JndiLdapContextFactory; /** * Created for org.apache.zeppelin.server on 09/06/16. */ -public class LdapGroupRealm extends JndiLdapRealm { +public class LdapGroupRealm extends JndiLdapRealm implements UserLookup { + private static final Logger LOG = LoggerFactory.getLogger(LdapGroupRealm.class); public AuthorizationInfo queryForAuthorizationInfo( @@ -91,4 +97,44 @@ public Set getRoleNamesForUser(String username, return new HashSet<>(); } + + /** + * function to extract users from LDAP + */ + @Override + public List lookupUsers(String searchText) { + List userList = new ArrayList<>(); + String userDnTemplate = getUserDnTemplate(); + String userDn[] = userDnTemplate.split(",", 2); + String userDnPrefix = userDn[0].split("=")[0]; + String userDnSuffix = userDn[1]; + JndiLdapContextFactory CF = (JndiLdapContextFactory) getContextFactory(); + try { + LdapContext ctx = CF.getSystemLdapContext(); + SearchControls constraints = new SearchControls(); + constraints.setSearchScope(SearchControls.SUBTREE_SCOPE); + String[] attrIDs = {userDnPrefix}; + constraints.setReturningAttributes(attrIDs); + NamingEnumeration result = ctx.search(userDnSuffix, "(" + userDnPrefix + "=*" + searchText + + "*)", constraints); + while (result.hasMore()) { + Attributes attrs = ((SearchResult) result.next()).getAttributes(); + if (attrs.get(userDnPrefix) != null) { + String currentUser = attrs.get(userDnPrefix).toString(); + userList.add(currentUser.split(":")[1].trim()); + } + } + } catch (Exception e) { + LOG.error("Error retrieving User list from Ldap Realm", e); + } + LOG.info("UserList: " + userList); + return userList; + } + + @Override + public Collection lookupRoles(String query) { + return Collections.EMPTY_SET; + } + + } diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java index 3381a516c5e..4ba80c3b2f8 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java @@ -44,6 +44,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -59,12 +60,14 @@ import javax.naming.PartialResultException; import javax.naming.SizeLimitExceededException; import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.Control; import javax.naming.ldap.LdapContext; import javax.naming.ldap.LdapName; import javax.naming.ldap.PagedResultsControl; +import org.apache.shiro.realm.ldap.JndiLdapContextFactory; /** @@ -120,7 +123,7 @@ *

securityManager.realms = $ldapRealm * */ -public class LdapRealm extends JndiLdapRealm { +public class LdapRealm extends JndiLdapRealm implements UserLookup { private static final SearchControls SUBTREE_SCOPE = new SearchControls(); private static final SearchControls ONELEVEL_SCOPE = new SearchControls(); @@ -878,4 +881,71 @@ private static final String expandTemplate(final String template, final Matcher } return output; } + + /** + * function to extract users from Zeppelin LdapRealm + */ + @Override + public List lookupUsers(String searchText) { + List userList = new ArrayList<>(); + if (log.isDebugEnabled()) { + log.debug("SearchText: " + searchText); + } + String userAttribute = getUserSearchAttributeName(); + String userSearchRealm = getUserSearchBase(); + String userObjectClass = getUserObjectClass(); + JndiLdapContextFactory CF = (JndiLdapContextFactory) getContextFactory(); + try { + LdapContext ctx = CF.getSystemLdapContext(); + SearchControls constraints = new SearchControls(); + constraints.setSearchScope(SearchControls.SUBTREE_SCOPE); + String[] attrIDs = {userAttribute}; + constraints.setReturningAttributes(attrIDs); + NamingEnumeration result = ctx.search(userSearchRealm, "(&(objectclass=" + + userObjectClass + ")(" + + userAttribute + "=" + searchText + "))", constraints); + while (result.hasMore()) { + Attributes attrs = ((SearchResult) result.next()).getAttributes(); + if (attrs.get(userAttribute) != null) { + String currentUser; + if (getUserLowerCase()) { + log.debug("userLowerCase true"); + currentUser = ((String) attrs.get(userAttribute).get()).toLowerCase(); + } else { + log.debug("userLowerCase false"); + currentUser = (String) attrs.get(userAttribute).get(); + } + if (log.isDebugEnabled()) { + log.debug("CurrentUser: " + currentUser); + } + userList.add(currentUser.trim()); + } + } + } catch (Exception e) { + log.error("Error retrieving User list from Ldap Realm", e); + } + return userList; + } + + /*** + * Get user roles from shiro.ini for Zeppelin LdapRealm + * @return + */ + @Override + public List lookupRoles(String searchText) { + List roleList = new ArrayList<>(); + Map roles = getListRoles(); + if (roles != null) { + Iterator it = roles.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = (Map.Entry) it.next(); + if (log.isDebugEnabled()) { + log.debug("RoleKeyValue: " + pair.getKey() + + " = " + pair.getValue()); + } + roleList.add((String) pair.getKey()); + } + } + return roleList; + } } diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/PamRealm.java b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/PamRealm.java index 5b3ae10070a..3c3f0534b2d 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/PamRealm.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/PamRealm.java @@ -23,10 +23,6 @@ import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; -import org.apache.shiro.crypto.hash.DefaultHashService; -import org.apache.shiro.crypto.hash.Hash; -import org.apache.shiro.crypto.hash.HashRequest; -import org.apache.shiro.crypto.hash.HashService; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.jvnet.libpam.PAM; @@ -43,7 +39,7 @@ */ public class PamRealm extends AuthorizingRealm { - private static final Logger LOG = LoggerFactory.getLogger(ZeppelinHubRealm.class); + private static final Logger LOG = LoggerFactory.getLogger(PamRealm.class); private String service; diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/UserLookup.java b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/UserLookup.java new file mode 100644 index 00000000000..b72f23bfc62 --- /dev/null +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/UserLookup.java @@ -0,0 +1,30 @@ +/* + * 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.zeppelin.realm; + +import java.util.Collection; + +/** + * + */ +public interface UserLookup { + + public Collection lookupRoles(String query); + + public Collection lookupUsers(String query); + +} diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java deleted file mode 100644 index f0e37404a19..00000000000 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java +++ /dev/null @@ -1,257 +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.zeppelin.rest; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.reflect.FieldUtils; -import org.apache.shiro.realm.jdbc.JdbcRealm; -import org.apache.shiro.realm.ldap.JndiLdapContextFactory; -import org.apache.shiro.realm.ldap.JndiLdapRealm; -import org.apache.shiro.realm.text.IniRealm; -import org.apache.shiro.util.JdbcUtils; -import org.apache.zeppelin.realm.ActiveDirectoryGroupRealm; -import org.apache.zeppelin.realm.LdapRealm; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.naming.NamingEnumeration; -import javax.naming.directory.Attributes; -import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; -import javax.naming.ldap.LdapContext; -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * This is class which help fetching users from different realms. - * getUserList() function is overloaded and according to the realm passed to the function it - * extracts users from its respective realm - */ -public class GetUserList { - - private static final Logger LOG = LoggerFactory.getLogger(GetUserList.class); - - /** - * function to extract users from shiro.ini - */ - public List getUserList(IniRealm r) { - List userList = new ArrayList<>(); - Map getIniUser = r.getIni().get("users"); - if (getIniUser != null) { - Iterator it = getIniUser.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry pair = (Map.Entry) it.next(); - userList.add(pair.getKey().toString().trim()); - } - } - return userList; - } - - - /*** - * Get user roles from shiro.ini - * @param r - * @return - */ - public List getRolesList(IniRealm r) { - List roleList = new ArrayList<>(); - Map getIniRoles = r.getIni().get("roles"); - if (getIniRoles != null) { - Iterator it = getIniRoles.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry pair = (Map.Entry) it.next(); - roleList.add(pair.getKey().toString().trim()); - } - } - return roleList; - } - - /** - * function to extract users from LDAP - */ - public List getUserList(JndiLdapRealm r, String searchText) { - List userList = new ArrayList<>(); - String userDnTemplate = r.getUserDnTemplate(); - String userDn[] = userDnTemplate.split(",", 2); - String userDnPrefix = userDn[0].split("=")[0]; - String userDnSuffix = userDn[1]; - JndiLdapContextFactory CF = (JndiLdapContextFactory) r.getContextFactory(); - try { - LdapContext ctx = CF.getSystemLdapContext(); - SearchControls constraints = new SearchControls(); - constraints.setSearchScope(SearchControls.SUBTREE_SCOPE); - String[] attrIDs = {userDnPrefix}; - constraints.setReturningAttributes(attrIDs); - NamingEnumeration result = ctx.search(userDnSuffix, "(" + userDnPrefix + "=*" + searchText + - "*)", constraints); - while (result.hasMore()) { - Attributes attrs = ((SearchResult) result.next()).getAttributes(); - if (attrs.get(userDnPrefix) != null) { - String currentUser = attrs.get(userDnPrefix).toString(); - userList.add(currentUser.split(":")[1].trim()); - } - } - } catch (Exception e) { - LOG.error("Error retrieving User list from Ldap Realm", e); - } - LOG.info("UserList: " + userList); - return userList; - } - - /** - * function to extract users from Zeppelin LdapRealm - */ - public List getUserList(LdapRealm r, String searchText) { - List userList = new ArrayList<>(); - if (LOG.isDebugEnabled()) { - LOG.debug("SearchText: " + searchText); - } - String userAttribute = r.getUserSearchAttributeName(); - String userSearchRealm = r.getUserSearchBase(); - String userObjectClass = r.getUserObjectClass(); - JndiLdapContextFactory CF = (JndiLdapContextFactory) r.getContextFactory(); - try { - LdapContext ctx = CF.getSystemLdapContext(); - SearchControls constraints = new SearchControls(); - constraints.setSearchScope(SearchControls.SUBTREE_SCOPE); - String[] attrIDs = {userAttribute}; - constraints.setReturningAttributes(attrIDs); - NamingEnumeration result = ctx.search(userSearchRealm, "(&(objectclass=" + - userObjectClass + ")(" - + userAttribute + "=" + searchText + "))", constraints); - while (result.hasMore()) { - Attributes attrs = ((SearchResult) result.next()).getAttributes(); - if (attrs.get(userAttribute) != null) { - String currentUser; - if (r.getUserLowerCase()) { - LOG.debug("userLowerCase true"); - currentUser = ((String) attrs.get(userAttribute).get()).toLowerCase(); - } else { - LOG.debug("userLowerCase false"); - currentUser = (String) attrs.get(userAttribute).get(); - } - if (LOG.isDebugEnabled()) { - LOG.debug("CurrentUser: " + currentUser); - } - userList.add(currentUser.trim()); - } - } - } catch (Exception e) { - LOG.error("Error retrieving User list from Ldap Realm", e); - } - return userList; - } - - /*** - * Get user roles from shiro.ini for Zeppelin LdapRealm - * @param r - * @return - */ - public List getRolesList(LdapRealm r) { - List roleList = new ArrayList<>(); - Map roles = r.getListRoles(); - if (roles != null) { - Iterator it = roles.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry pair = (Map.Entry) it.next(); - if (LOG.isDebugEnabled()) { - LOG.debug("RoleKeyValue: " + pair.getKey() + - " = " + pair.getValue()); - } - roleList.add((String) pair.getKey()); - } - } - return roleList; - } - - - public List getUserList(ActiveDirectoryGroupRealm r, String searchText) { - List userList = new ArrayList<>(); - try { - LdapContext ctx = r.getLdapContextFactory().getSystemLdapContext(); - userList = r.searchForUserName(searchText, ctx); - } catch (Exception e) { - LOG.error("Error retrieving User list from ActiveDirectory Realm", e); - } - return userList; - } - - /** - * function to extract users from JDBCs - */ - public List getUserList(JdbcRealm obj) { - List userlist = new ArrayList<>(); - PreparedStatement ps = null; - ResultSet rs = null; - DataSource dataSource = null; - String authQuery = ""; - String retval[]; - String tablename = ""; - String username = ""; - String userquery = ""; - try { - dataSource = (DataSource) FieldUtils.readField(obj, "dataSource", true); - authQuery = (String) FieldUtils.readField(obj, "DEFAULT_AUTHENTICATION_QUERY", true); - LOG.info(authQuery); - String authQueryLowerCase = authQuery.toLowerCase(); - retval = authQueryLowerCase.split("from", 2); - if (retval.length >= 2) { - retval = retval[1].split("with|where", 2); - tablename = retval[0]; - retval = retval[1].split("where", 2); - if (retval.length >= 2) - retval = retval[1].split("=", 2); - else - retval = retval[0].split("=", 2); - username = retval[0]; - } - - if (StringUtils.isBlank(username) || StringUtils.isBlank(tablename)) { - return userlist; - } - - userquery = "select " + username + " from " + tablename; - - } catch (IllegalAccessException e) { - LOG.error("Error while accessing dataSource for JDBC Realm", e); - return null; - } - - try { - Connection con = dataSource.getConnection(); - ps = con.prepareStatement(userquery); - rs = ps.executeQuery(); - while (rs.next()) { - userlist.add(rs.getString(1).trim()); - } - } catch (Exception e) { - LOG.error("Error retrieving User list from JDBC Realm", e); - } finally { - JdbcUtils.closeResultSet(rs); - JdbcUtils.closeStatement(ps); - } - return userlist; - } - -} diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/LoginRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/LoginRestApi.java index e90954142fd..81aef9f06c9 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/LoginRestApi.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/LoginRestApi.java @@ -32,7 +32,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.Response; import java.util.HashMap; -import java.util.HashSet; +import java.util.Set; import java.util.Map; /** @@ -79,7 +79,7 @@ public Response postLogin(@FormParam("userName") String userName, currentUser.getSession(true); currentUser.login(token); - HashSet roles = SecurityUtils.getRoles(); + Set roles = SecurityUtils.getRoles(); String principal = SecurityUtils.getPrincipal(); String ticket; if ("anonymous".equals(principal)) diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java index 9c511d46fec..8169b147e4e 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java @@ -123,8 +123,7 @@ private String getBlockNotAuthenticatedUserErrorMsg() throws IOException { * Check if the current user is not authenticated(anonymous user) or not */ private void checkIfUserIsAnon(String errorMsg) { - boolean isAuthenticated = SecurityUtils.isAuthenticated(); - if (isAuthenticated && SecurityUtils.getPrincipal().equals("anonymous")) { + if (SecurityUtils.isAnonymous()) { LOG.info("Anonymous user cannot set any permissions for this note."); throw new ForbiddenException(errorMsg); } @@ -187,7 +186,7 @@ private void checkIfParagraphIsNotNull(Paragraph paragraph) { public Response putNotePermissions(@PathParam("noteId") String noteId, String req) throws IOException { String principal = SecurityUtils.getPrincipal(); - HashSet roles = SecurityUtils.getRoles(); + Set roles = SecurityUtils.getRoles(); HashSet userAndRoles = new HashSet<>(); userAndRoles.add(principal); userAndRoles.addAll(roles); @@ -272,7 +271,7 @@ public Response bind(@PathParam("noteId") String noteId) { @ZeppelinApi public Response getNoteList() throws IOException { AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); - HashSet userAndRoles = SecurityUtils.getRoles(); + Set userAndRoles = SecurityUtils.getRoles(); userAndRoles.add(subject.getUser()); List> notesInfo = notebookServer.generateNotesInfo(false, subject, userAndRoles); @@ -913,7 +912,7 @@ public Response getUpdatedJobListforNote( public Response search(@QueryParam("q") String queryTerm) { LOG.info("Searching notes for: {}", queryTerm); String principal = SecurityUtils.getPrincipal(); - HashSet roles = SecurityUtils.getRoles(); + Set roles = SecurityUtils.getRoles(); HashSet userAndRoles = new HashSet<>(); userAndRoles.add(principal); userAndRoles.addAll(roles); diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java index 742af9e5241..548f3b90ba7 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java @@ -17,16 +17,8 @@ package org.apache.zeppelin.rest; - -import org.apache.commons.lang3.StringUtils; -import org.apache.shiro.realm.Realm; -import org.apache.shiro.realm.jdbc.JdbcRealm; -import org.apache.shiro.realm.ldap.JndiLdapRealm; -import org.apache.shiro.realm.text.IniRealm; import org.apache.zeppelin.annotation.ZeppelinApi; import org.apache.zeppelin.conf.ZeppelinConfiguration; -import org.apache.zeppelin.realm.ActiveDirectoryGroupRealm; -import org.apache.zeppelin.realm.LdapRealm; import org.apache.zeppelin.server.JsonResponse; import org.apache.zeppelin.ticket.TicketContainer; import org.apache.zeppelin.utils.SecurityUtils; @@ -69,7 +61,7 @@ public SecurityRestApi() { public Response ticket() { ZeppelinConfiguration conf = ZeppelinConfiguration.create(); String principal = SecurityUtils.getPrincipal(); - HashSet roles = SecurityUtils.getRoles(); + Set roles = SecurityUtils.getRoles(); JsonResponse response; // ticket set to anonymous for anonymous user. Simplify testing. String ticket; @@ -98,73 +90,12 @@ public Response ticket() { @Path("userlist/{searchText}") public Response getUserList(@PathParam("searchText") final String searchText) { - List usersList = new ArrayList<>(); - List rolesList = new ArrayList<>(); - try { - GetUserList getUserListObj = new GetUserList(); - Collection realmsList = SecurityUtils.getRealmsList(); - if (realmsList != null) { - for (Iterator iterator = realmsList.iterator(); iterator.hasNext(); ) { - Realm realm = iterator.next(); - String name = realm.getClass().getName(); - if (LOG.isDebugEnabled()) { - LOG.debug("RealmClass.getName: " + name); - } - if (name.equals("org.apache.shiro.realm.text.IniRealm")) { - usersList.addAll(getUserListObj.getUserList((IniRealm) realm)); - rolesList.addAll(getUserListObj.getRolesList((IniRealm) realm)); - } else if (name.equals("org.apache.zeppelin.realm.LdapGroupRealm")) { - usersList.addAll(getUserListObj.getUserList((JndiLdapRealm) realm, searchText)); - } else if (name.equals("org.apache.zeppelin.realm.LdapRealm")) { - usersList.addAll(getUserListObj.getUserList((LdapRealm) realm, searchText)); - rolesList.addAll(getUserListObj.getRolesList((LdapRealm) realm)); - } else if (name.equals("org.apache.zeppelin.realm.ActiveDirectoryGroupRealm")) { - usersList.addAll(getUserListObj.getUserList((ActiveDirectoryGroupRealm) realm, - searchText)); - } else if (name.equals("org.apache.shiro.realm.jdbc.JdbcRealm")) { - usersList.addAll(getUserListObj.getUserList((JdbcRealm) realm)); - } - } - } - } catch (Exception e) { - LOG.error("Exception in retrieving Users from realms ", e); - } - List autoSuggestUserList = new ArrayList<>(); - List autoSuggestRoleList = new ArrayList<>(); - Collections.sort(usersList); - Collections.sort(rolesList); - Collections.sort(usersList, new Comparator() { - @Override - public int compare(String o1, String o2) { - if (o1.matches(searchText + "(.*)") && o2.matches(searchText + "(.*)")) { - return 0; - } else if (o1.matches(searchText + "(.*)")) { - return -1; - } - return 0; - } - }); - int maxLength = 0; - for (String user : usersList) { - if (StringUtils.containsIgnoreCase(user, searchText)) { - autoSuggestUserList.add(user); - maxLength++; - } - if (maxLength == 5) { - break; - } - } - - for (String role : rolesList) { - if (StringUtils.containsIgnoreCase(role, searchText)) { - autoSuggestRoleList.add(role); - } - } - - Map returnListMap = new HashMap<>(); - returnListMap.put("users", autoSuggestUserList); - returnListMap.put("roles", autoSuggestRoleList); - + Collection usersList = SecurityUtils.lookupUsersInRealms(searchText); + Collection rolesList = SecurityUtils.lookupRolesInRealms(searchText); + + Map returnListMap = new HashMap<>(); + returnListMap.put("users", usersList); + returnListMap.put("roles", rolesList); return new JsonResponse<>(Response.Status.OK, "", returnListMap).build(); } diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/utils/SecurityUtils.java b/zeppelin-server/src/main/java/org/apache/zeppelin/utils/SecurityUtils.java index dcb5a1f339d..4f595a42df8 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/utils/SecurityUtils.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/utils/SecurityUtils.java @@ -24,22 +24,30 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; -import java.util.Map; - +import java.util.Set; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; -import org.apache.shiro.realm.text.IniRealm; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ThreadContext; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.zeppelin.conf.ZeppelinConfiguration; -import org.apache.zeppelin.realm.LdapRealm; -import org.mortbay.log.Log; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import com.google.common.collect.Sets; +import java.lang.reflect.Method; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.realm.text.IniRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.zeppelin.realm.UserLookup; /** * Tools for securing Zeppelin @@ -50,6 +58,10 @@ public class SecurityUtils { private static final HashSet EMPTY_HASHSET = Sets.newHashSet(); private static boolean isEnabled = false; private static final Logger log = LoggerFactory.getLogger(SecurityUtils.class); + private static final int MAX_MATCHES_FROM_LOOKUP = 5; + private static final String INI_SECTION_USERS = "users"; + private static final String INI_SECTION_ROLES = "roles"; + public static void initSecurityManager(String shiroPath) { IniSecurityManagerFactory factory = new IniSecurityManagerFactory("file:" + shiroPath); @@ -120,33 +132,144 @@ public static HashSet getRoles() { } Subject subject = org.apache.shiro.SecurityUtils.getSubject(); HashSet roles = new HashSet<>(); - Map allRoles = null; if (subject.isAuthenticated()) { + PrincipalCollection principals = subject.getPrincipals(); + Collection realmsList = SecurityUtils.getRealmsList(); for (Iterator iterator = realmsList.iterator(); iterator.hasNext(); ) { Realm realm = iterator.next(); - String name = realm.getClass().getName(); - if (name.equals("org.apache.shiro.realm.text.IniRealm")) { - allRoles = ((IniRealm) realm).getIni().get("roles"); - break; - } else if (name.equals("org.apache.zeppelin.realm.LdapRealm")) { - allRoles = ((LdapRealm) realm).getListRoles(); - break; - } - } - if (allRoles != null) { - Iterator it = allRoles.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry pair = (Map.Entry) it.next(); - if (subject.hasRole((String) pair.getKey())) { - roles.add((String) pair.getKey()); + if (realm instanceof AuthorizingRealm) { + AuthorizingRealm authRealm = (AuthorizingRealm) realm; + AuthorizationInfo info = extractAuthorizationInfoOrNull(authRealm, principals); + if (info != null) { + roles.addAll(info.getRoles()); } } } + } return roles; } + + /** + * Uses reflection to call a protected method on the AuthorizingRealm class to + * work around SHIRO-492. + * + * @param authRealm The realm to query + * @param principals The principals of the authenticated user + * @return AuthorizationInfo of the authenticated user or null + */ + private static AuthorizationInfo extractAuthorizationInfoOrNull( + AuthorizingRealm authRealm, PrincipalCollection principals) { + try { + Method method = AuthorizingRealm.class + .getDeclaredMethod("getAuthorizationInfo", PrincipalCollection.class); + method.setAccessible(true); + return (AuthorizationInfo) method.invoke(authRealm, principals); + } catch (Exception e) { + log.warn("Unable to extract authorization info from realm {}", authRealm.getName(), e); + return null; + } + } + + public static Collection lookupUsersInRealms(final String searchText) { + Set userList = new TreeSet<>(new PreferStartsWithComparator(searchText)); + + // start by adding the authenticated user's name + userList.add(getPrincipal()); + + // check if the configured realms support lookup + Collection realmsList = SecurityUtils.getRealmsList(); + if (realmsList != null) { + for (Iterator iterator = realmsList.iterator(); iterator.hasNext(); ) { + Realm realm = iterator.next(); + if (realm instanceof UserLookup) { + UserLookup lookup = (UserLookup) realm; + userList.addAll(lookup.lookupUsers(searchText)); + } else if (realm instanceof IniRealm) { + // special handling for ini realm because it is the default + // when no other specific realm has been defined + IniRealm r = (IniRealm) realm; + userList.addAll(getIniUsersOrRoles(r, INI_SECTION_USERS)); + } + } + } + + // remove any users that don't contain the search text + // this filters realms that didn't actually apply the filter + for (Iterator it = userList.iterator(); it.hasNext();) { + if (!StringUtils.containsIgnoreCase(it.next(), searchText)) { + it.remove(); + } + } + + // return only the top N matches + List topResults = new ArrayList(); + int remaining = MAX_MATCHES_FROM_LOOKUP; + for (Iterator it = userList.iterator(); remaining-- > 0 && it.hasNext();) { + topResults.add(it.next()); + } + return topResults; + } + + public static Collection lookupRolesInRealms(String searchText) { + Set rolesList = new TreeSet<>(new PreferStartsWithComparator(searchText)); + + // start by adding the authenticated user's roles + rolesList.addAll(getRoles()); + + // check if the configured realms support lookup + Collection realmsList = SecurityUtils.getRealmsList(); + if (realmsList != null) { + for (Iterator iterator = realmsList.iterator(); iterator.hasNext(); ) { + Realm realm = iterator.next(); + if (realm instanceof UserLookup) { + UserLookup lookup = (UserLookup) realm; + rolesList.addAll(lookup.lookupRoles(searchText)); + } else if (realm instanceof IniRealm) { + // special handling for ini realm because it is the default + // when no other specific realm has been defined + IniRealm r = (IniRealm) realm; + rolesList.addAll(getIniUsersOrRoles(r, INI_SECTION_ROLES)); + } + } + } + + // remove any roles that don't contain the search text + // this filters the authenticated user's roles and if one of the + // realms didn't actually apply the filter + for (Iterator it = rolesList.iterator(); it.hasNext();) { + if (!StringUtils.containsIgnoreCase(it.next(), searchText)) { + it.remove(); + } + } + + // return only the top N matches + List topResults = new ArrayList(); + int remaining = MAX_MATCHES_FROM_LOOKUP; + for (Iterator it = rolesList.iterator(); remaining-- > 0 && it.hasNext();) { + topResults.add(it.next()); + } + return topResults; + } + + /** + * Return a collection of the user or role names configured in the IniRealm. + * @param realm + * @param section "users" or "roles" + * @return A collection of the user or role names. Possibly empty but not null. + */ + private static Collection getIniUsersOrRoles(IniRealm realm, String section) { + Set matches = new HashSet<>(); + Map entries = realm.getIni().get(section); + if (entries != null) { + for (Object key : entries.keySet()) { + matches.add(key.toString().trim()); + } + } + return matches; + } /** * Checked if shiro enabled or not @@ -157,4 +280,37 @@ public static boolean isAuthenticated() { } return org.apache.shiro.SecurityUtils.getSubject().isAuthenticated(); } + + public static boolean isAnonymous() { + return isAuthenticated() && ANONYMOUS.equals(getPrincipal()); + } + + /** + * A Comparator that sorts matches, but prefer matches that start with the + * search text over those that just contain the search text + */ + static class PreferStartsWithComparator implements Comparator { + + private final String searchText; + private final Collator collator = Collator.getInstance(); + + PreferStartsWithComparator(String searchText) { + this.searchText = searchText; + } + + @Override + public int compare(String o1, String o2) { + boolean o1StartsWith = StringUtils.startsWithIgnoreCase(o1, searchText); + boolean o2StartsWith = StringUtils.startsWithIgnoreCase(o2, searchText); + if (o1StartsWith && o2StartsWith) { + return collator.compare(o1, o2); + } else if (o1StartsWith) { + return -1; + } else if (o2StartsWith) { + return 1; + } else { + return collator.compare(o1, o2); + } + } + } } From e14b221b5877272d1ce453d1b470468b88985a5d Mon Sep 17 00:00:00 2001 From: Thomas Grant Date: Thu, 8 Jun 2017 22:20:15 -0400 Subject: [PATCH 3/5] Using java.security.Principal.getName() if appropriate --- .../java/org/apache/zeppelin/utils/SecurityUtils.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/utils/SecurityUtils.java b/zeppelin-server/src/main/java/org/apache/zeppelin/utils/SecurityUtils.java index 4f595a42df8..e602ed5c6fe 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/utils/SecurityUtils.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/utils/SecurityUtils.java @@ -20,6 +20,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; +import java.security.Principal; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -102,7 +103,12 @@ public static String getPrincipal() { String principal; if (subject.isAuthenticated()) { - principal = subject.getPrincipal().toString(); + Object principalObj = subject.getPrincipal(); + if (principalObj instanceof Principal) { + principal = ((Principal) principalObj).getName(); + } else { + principal = String.valueOf(principalObj); + } } else { principal = ANONYMOUS; } From 0d31b446e508ec6421d8705c85df573a2aece9b7 Mon Sep 17 00:00:00 2001 From: Thomas Grant Date: Thu, 8 Jun 2017 22:21:14 -0400 Subject: [PATCH 4/5] Securing the entire application, not just /api/* --- .../main/java/org/apache/zeppelin/server/ZeppelinServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java index 826ae5f863f..6189e10930c 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java @@ -329,7 +329,7 @@ private static void setupRestApiContextHandler(WebAppContext webapp, if (!StringUtils.isBlank(shiroIniPath)) { webapp.setInitParameter("shiroConfigLocations", new File(shiroIniPath).toURI().toString()); SecurityUtils.initSecurityManager(shiroIniPath); - webapp.addFilter(ShiroFilter.class, "/api/*", EnumSet.allOf(DispatcherType.class)); + webapp.addFilter(ShiroFilter.class, "/*", EnumSet.allOf(DispatcherType.class)); webapp.addEventListener(new EnvironmentLoaderListener()); } } From 2f87887860aaca9f03e206898687758ac20cdae8 Mon Sep 17 00:00:00 2001 From: Thomas Grant Date: Fri, 9 Jun 2017 15:02:40 -0400 Subject: [PATCH 5/5] Adding a browser endpoint that can be protected by shiro.ini to trigger indirect login attempts --- .../zeppelin/server/IndirectLoginServlet.java | 22 ++++++++++++ .../zeppelin/server/ZeppelinServer.java | 34 ++++++++++--------- 2 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 zeppelin-server/src/main/java/org/apache/zeppelin/server/IndirectLoginServlet.java diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/IndirectLoginServlet.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/IndirectLoginServlet.java new file mode 100644 index 00000000000..bc34a1706ec --- /dev/null +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/IndirectLoginServlet.java @@ -0,0 +1,22 @@ +package org.apache.zeppelin.server; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * This servlet redirects the user back to the application root + * after a successful indirect login. This is a browser addressable link, + * not a restful web service. + */ +public class IndirectLoginServlet extends HttpServlet { + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + response.sendRedirect(request.getContextPath()); + } + +} diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java index 6189e10930c..9b96b0c5cd0 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java @@ -314,24 +314,15 @@ private static SslContextFactory getSslContextFactory(ZeppelinConfiguration conf private static void setupRestApiContextHandler(WebAppContext webapp, ZeppelinConfiguration conf) { - final ServletHolder servletHolder = new ServletHolder( new org.glassfish.jersey.servlet.ServletContainer()); - servletHolder.setInitParameter("javax.ws.rs.Application", ZeppelinServer.class.getName()); servletHolder.setName("rest"); servletHolder.setForcedPath("rest"); - + webapp.setSessionHandler(new SessionHandler()); webapp.addServlet(servletHolder, "/api/*"); - - String shiroIniPath = conf.getShiroPath(); - if (!StringUtils.isBlank(shiroIniPath)) { - webapp.setInitParameter("shiroConfigLocations", new File(shiroIniPath).toURI().toString()); - SecurityUtils.initSecurityManager(shiroIniPath); - webapp.addFilter(ShiroFilter.class, "/*", EnumSet.allOf(DispatcherType.class)); - webapp.addEventListener(new EnvironmentLoaderListener()); - } + } private static WebAppContext setupWebAppContext(ContextHandlerCollection contexts, @@ -339,10 +330,10 @@ private static WebAppContext setupWebAppContext(ContextHandlerCollection context WebAppContext webApp = new WebAppContext(); webApp.setContextPath(conf.getServerContextPath()); + contexts.addHandler(webApp); File warPath = new File(conf.getString(ConfVars.ZEPPELIN_WAR)); if (warPath.isDirectory()) { // Development mode, read from FS - // webApp.setDescriptor(warPath+"/WEB-INF/web.xml"); webApp.setResourceBase(warPath.getPath()); webApp.setParentLoaderPriority(true); } else { @@ -353,18 +344,29 @@ private static WebAppContext setupWebAppContext(ContextHandlerCollection context LOG.info("ZeppelinServer Webapp path: {}", warTempDirectory.getPath()); webApp.setTempDirectory(warTempDirectory); } - // Explicit bind to root + + // Explicitly bind the default servlet to root webApp.addServlet(new ServletHolder(new DefaultServlet()), "/*"); - contexts.addHandler(webApp); - + + // Add redirecting servlet to support indirect logins + webApp.addServlet(new ServletHolder(new IndirectLoginServlet()), "/xlogin"); + webApp.addFilter(new FilterHolder(CorsFilter.class), "/*", EnumSet.allOf(DispatcherType.class)); webApp.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", Boolean.toString(conf.getBoolean(ConfVars.ZEPPELIN_SERVER_DEFAULT_DIR_ALLOWED))); + + // Enable shiro security filter + String shiroIniPath = conf.getShiroPath(); + if (!StringUtils.isBlank(shiroIniPath)) { + webApp.setInitParameter("shiroConfigLocations", new File(shiroIniPath).toURI().toString()); + SecurityUtils.initSecurityManager(shiroIniPath); + webApp.addFilter(ShiroFilter.class, "/*", EnumSet.allOf(DispatcherType.class)); + webApp.addEventListener(new EnvironmentLoaderListener()); + } return webApp; - } @Override