Repository: zeppelin Updated Branches: refs/heads/master 1c3373937 -> 24922e103
[Zeppelin 946] Permissions not honoring group ### What is this PR for? Error: Insufficient privileges to write notebook. Allowed users or roles: [admin, zeppelinWrite] But the user randerson belongs to: [randerson] It's seems clear that user randerson isn't mapped to any roles, or groups (even though he of course is a member of the zeppelinWrite group in AD and as a result also part of the local admin Role). A TCPDUMP reveals that during login, all of my group memberships are in fact returned during the ldap bind operation. However, when I attempt to modify a notebook, a call is never made to AD, to pull back my group memberships. It doesn't seem to look at my local group memberships (/etc/group) either. ### What type of PR is it? [Bug Fix] ### Todos * [x] - fix for permissions not honoring group * [x] - read roles from shiro.ini * [x] - at times group name was displaying instead of user/principal name. * [x] - doc ### What is the Jira issue? [ZEPPELIN-946](https://issues.apache.org/jira/browse/ZEPPELIN-946) ### Screenshots/How should this be tested? Use one of the following setting for IniRealm, LDAP or AD in shiro.ini [main] admin = password1, admin finance1 = finance1, finance finance2 = finance2, finance hr1 = hr1, hr hr2 = hr2, hr activeDirectoryRealm = org.apache.zeppelin.server.ActiveDirectoryGroupRealm activeDirectoryRealm.systemUsername = userNameA activeDirectoryRealm.systemPassword = passwordA activeDirectoryRealm.searchBase = CN=Users,DC=SOME_GROUP,DC=COMPANY,DC=COM activeDirectoryRealm.url = ldap://ldap.test.com:389 activeDirectoryRealm.groupRolesMap = "CN=admin,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"admin","CN=finance,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"finance","CN=hr,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"hr" activeDirectoryRealm.authorizationCachingEnabled = false ldapRealm = org.apache.zeppelin.server.LdapGroupRealm # search base for ldap groups (only relevant for LdapGroupRealm): ldapRealm.contextFactory.environment[ldap.searchBase] = dc=COMPANY,dc=COM ldapRealm.contextFactory.url = ldap://ldap.test.com:389 ldapRealm.userDnTemplate = uid={0},ou=Users,dc=COMPANY,dc=COM ldapRealm.contextFactory.authenticationMechanism = SIMPLE [roles] admin = * hr = * finance = * group1 = * [urls] /api/version = anon /** = authc Login as user1 (say finance1), and set a permission of a notebook as "finance" <img width="1282" alt="screen shot 2016-06-11 at 9 50 32 am" src="https://cloud.githubusercontent.com/assets/674497/15983178/aad710ee-2fbc-11e6-861d-508ecc8c7b74.png"> Save setting <img width="1281" alt="screen shot 2016-06-11 at 9 51 05 am" src="https://cloud.githubusercontent.com/assets/674497/15983180/aad86ea8-2fbc-11e6-8b68-4571496ec733.png"> Now logout and login as user2 (say finance2) which belong to the same group as above "finance", verify that you have access to the same notebook. <img width="1282" alt="screen shot 2016-06-11 at 9 51 25 am" src="https://cloud.githubusercontent.com/assets/674497/15983181/aad9a78c-2fbc-11e6-8a41-a3dc108cabdc.png"> Logout and login again, this time as a user that does not belong to the group "finance", a user say hr1. Verify that this user does not have permission to view the same notebook. <img width="1281" alt="screen shot 2016-06-11 at 9 51 42 am" src="https://cloud.githubusercontent.com/assets/674497/15983179/aad7794e-2fbc-11e6-9002-f7b0fc54ac59.png"> ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Prabhjyot Singh <[email protected]> Closes #986 from prabhjyotsingh/ZEPPELIN-946 and squashes the following commits: e04c145 [Prabhjyot Singh] add sample LDAP and AD realm setting in comments 3e443d7 [Prabhjyot Singh] imporoving performance of ActiveDirectoryGroupRealm 188ac17 [Prabhjyot Singh] activeDirectoryRealm.principalSuffix isn't honoured 293853e [Prabhjyot Singh] fix failing selenium test case 8d41149 [Prabhjyot Singh] try maximize browser 41bb23b [Prabhjyot Singh] selenium test case 3149417 [Prabhjyot Singh] Merge remote-tracking branch 'origin/master' into ZEPPELIN-946 310a81d [Prabhjyot Singh] make `[roles]` optional in shiro.ini 966a96c [Prabhjyot Singh] update doc ed54a92 [Prabhjyot Singh] read roles from shiro.ini e8f1f97 [Prabhjyot Singh] fix for permissions not honoring group 4194f93 [Prabhjyot Singh] sometime it dispalys groupName instead of principal Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/24922e10 Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/24922e10 Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/24922e10 Branch: refs/heads/master Commit: 24922e1036c5e410b676fd9b513d008cb046424e Parents: 1c33739 Author: Prabhjyot Singh <[email protected]> Authored: Fri Jun 17 07:35:21 2016 +0530 Committer: Prabhjyot Singh <[email protected]> Committed: Fri Jun 17 14:48:57 2016 +0530 ---------------------------------------------------------------------- conf/shiro.ini | 31 ++- docs/security/shiroauthentication.md | 32 +++ .../apache/zeppelin/rest/SecurityRestApi.java | 20 +- .../server/ActiveDirectoryGroupRealm.java | 241 +++++++++++++++++++ .../apache/zeppelin/server/LdapGroupRealm.java | 94 ++++++++ .../apache/zeppelin/socket/NotebookServer.java | 43 ++-- .../apache/zeppelin/utils/SecurityUtils.java | 49 +++- .../org/apache/zeppelin/AbstractZeppelinIT.java | 4 +- .../org/apache/zeppelin/WebDriverManager.java | 3 +- .../org/apache/zeppelin/ZeppelinITUtils.java | 7 + .../zeppelin/integration/AuthenticationIT.java | 209 ++++++++++++++++ .../src/app/notebook/notebook-actionBar.html | 21 +- 12 files changed, 700 insertions(+), 54 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zeppelin/blob/24922e10/conf/shiro.ini ---------------------------------------------------------------------- diff --git a/conf/shiro.ini b/conf/shiro.ini index 61ee964..ced9776 100644 --- a/conf/shiro.ini +++ b/conf/shiro.ini @@ -25,19 +25,44 @@ user3 = password4, role2 # Sample LDAP configuration, for user Authentication, currently tested for single Realm [main] -#ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm -#ldapRealm.userDnTemplate = cn={0},cn=engg,ou=testdomain,dc=testdomain,dc=com -#ldapRealm.contextFactory.url = ldap://ldaphost:389 +### A sample for configuring Active Directory Realm +#activeDirectoryRealm = org.apache.zeppelin.server.ActiveDirectoryGroupRealm +#activeDirectoryRealm.systemUsername = userNameA +#activeDirectoryRealm.systemPassword = passwordA +#activeDirectoryRealm.searchBase = CN=Users,DC=SOME_GROUP,DC=COMPANY,DC=COM +#activeDirectoryRealm.url = ldap://ldap.test.com:389 +#activeDirectoryRealm.groupRolesMap = "CN=admin,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"admin","CN=finance,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"finance","CN=hr,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"hr" +#activeDirectoryRealm.authorizationCachingEnabled = false + +### A sample for configuring LDAP Directory Realm +#ldapRealm = org.apache.zeppelin.server.LdapGroupRealm +## search base for ldap groups (only relevant for LdapGroupRealm): +#ldapRealm.contextFactory.environment[ldap.searchBase] = dc=COMPANY,dc=COM +#ldapRealm.contextFactory.url = ldap://ldap.test.com:389 +#ldapRealm.userDnTemplate = uid={0},ou=Users,dc=COMPANY,dc=COM #ldapRealm.contextFactory.authenticationMechanism = SIMPLE + + sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager + +### If caching of user is required then uncomment below lines +#cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager +#securityManager.cacheManager = $cacheManager + securityManager.sessionManager = $sessionManager # 86,400,000 milliseconds = 24 hour securityManager.sessionManager.globalSessionTimeout = 86400000 shiro.loginUrl = /api/login +[roles] +role1 = * +role2 = * +role3 = * + [urls] # anon means the access is anonymous. # authcBasic means Basic Auth Security +# authc means Form based Auth Security # To enfore security, comment the line below and uncomment the next one /api/version = anon /** = anon http://git-wip-us.apache.org/repos/asf/zeppelin/blob/24922e10/docs/security/shiroauthentication.md ---------------------------------------------------------------------- diff --git a/docs/security/shiroauthentication.md b/docs/security/shiroauthentication.md index 646d740..969e2f4 100644 --- a/docs/security/shiroauthentication.md +++ b/docs/security/shiroauthentication.md @@ -69,4 +69,36 @@ user2 = password3 Those combinations are defined in the `conf/shiro.ini` file. +####5. Groups and permissions (optional) +In case you want to leverage user groups and permissions, use one of the following configuration for LDAP or AD under `[main]` segment of shiro.ini + +``` +activeDirectoryRealm = org.apache.zeppelin.server.ActiveDirectoryGroupRealm +activeDirectoryRealm.systemUsername = userNameA +activeDirectoryRealm.systemPassword = passwordA +activeDirectoryRealm.searchBase = CN=Users,DC=SOME_GROUP,DC=COMPANY,DC=COM +activeDirectoryRealm.url = ldap://ldap.test.com:389 +activeDirectoryRealm.groupRolesMap = "CN=aGroupName,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"group1" +activeDirectoryRealm.authorizationCachingEnabled = false + +ldapRealm = org.apache.zeppelin.server.LdapGroupRealm +# search base for ldap groups (only relevant for LdapGroupRealm): +ldapRealm.contextFactory.environment[ldap.searchBase] = dc=COMPANY,dc=COM +ldapRealm.contextFactory.url = ldap://ldap.test.com:389 +ldapRealm.userDnTemplate = uid={0},ou=Users,dc=COMPANY,dc=COM +ldapRealm.contextFactory.authenticationMechanism = SIMPLE +``` + +also define roles/groups that you want to have in system, like below; + +``` +[roles] +admin = * +hr = * +finance = * +group1 = * +``` + +All of above configurations are defined in the `conf/shiro.ini` file. + > **NOTE :** This documentation is originally from > [SECURITY-README.md](https://github.com/apache/zeppelin/blob/master/SECURITY-README.md). http://git-wip-us.apache.org/repos/asf/zeppelin/blob/24922e10/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java ---------------------------------------------------------------------- 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 e344956..11c8f96 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 @@ -22,8 +22,6 @@ 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.shiro.util.ThreadContext; -import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.zeppelin.annotation.ZeppelinApi; import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.server.JsonResponse; @@ -41,7 +39,6 @@ import java.util.*; /** * Zeppelin security rest api endpoint. - * */ @Path("/security") @Produces("application/json") @@ -101,19 +98,16 @@ public class SecurityRestApi { List<String> usersList = new ArrayList<>(); try { GetUserList getUserListObj = new GetUserList(); - DefaultWebSecurityManager defaultWebSecurityManager; - String key = ThreadContext.SECURITY_MANAGER_KEY; - defaultWebSecurityManager = (DefaultWebSecurityManager) ThreadContext.get(key); - Collection<Realm> realms = defaultWebSecurityManager.getRealms(); - List realmsList = new ArrayList(realms); - for (int i = 0; i < realmsList.size(); i++) { - String name = ((Realm) realmsList.get(i)).getName(); + Collection realmsList = SecurityUtils.getRealmsList(); + for (Iterator<Realm> iterator = realmsList.iterator(); iterator.hasNext(); ) { + Realm realm = iterator.next(); + String name = realm.getName(); if (name.equals("iniRealm")) { - usersList.addAll(getUserListObj.getUserList((IniRealm) realmsList.get(i))); + usersList.addAll(getUserListObj.getUserList((IniRealm) realm)); } else if (name.equals("ldapRealm")) { - usersList.addAll(getUserListObj.getUserList((JndiLdapRealm) realmsList.get(i))); + usersList.addAll(getUserListObj.getUserList((JndiLdapRealm) realm)); } else if (name.equals("jdbcRealm")) { - usersList.addAll(getUserListObj.getUserList((JdbcRealm) realmsList.get(i))); + usersList.addAll(getUserListObj.getUserList((JdbcRealm) realm)); } } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/24922e10/zeppelin-server/src/main/java/org/apache/zeppelin/server/ActiveDirectoryGroupRealm.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ActiveDirectoryGroupRealm.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ActiveDirectoryGroupRealm.java new file mode 100644 index 0000000..fc3ccc8 --- /dev/null +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ActiveDirectoryGroupRealm.java @@ -0,0 +1,241 @@ +/* + * 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.server; + +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.realm.ldap.AbstractLdapRealm; +import org.apache.shiro.realm.ldap.LdapContextFactory; +import org.apache.shiro.realm.ldap.LdapUtils; +import org.apache.shiro.subject.PrincipalCollection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.LdapContext; +import java.util.*; + + +/** + * A {@link Realm} that authenticates with an active directory LDAP + * server to determine the roles for a particular user. This implementation + * queries for the user's groups and then maps the group names to roles using the + * {@link #groupRolesMap}. + * + * @since 0.1 + */ +public class ActiveDirectoryGroupRealm extends AbstractLdapRealm { + + private static final Logger log = LoggerFactory.getLogger(ActiveDirectoryGroupRealm.class); + + private static final String ROLE_NAMES_DELIMETER = ","; + + /*-------------------------------------------- + | I N S T A N C E V A R I A B L E S | + ============================================*/ + + /** + * Mapping from fully qualified active directory + * group names (e.g. CN=Group,OU=Company,DC=MyDomain,DC=local) + * as returned by the active directory LDAP server to role names. + */ + private Map<String, String> groupRolesMap; + + /*-------------------------------------------- + | C O N S T R U C T O R S | + ============================================*/ + + public void setGroupRolesMap(Map<String, String> groupRolesMap) { + this.groupRolesMap = groupRolesMap; + } + + /*-------------------------------------------- + | M E T H O D S | + ============================================*/ + + + /** + * Builds an {@link AuthenticationInfo} object by querying the active directory LDAP context for + * the specified username. This method binds to the LDAP server using the provided username + * and password - which if successful, indicates that the password is correct. + * <p/> + * This method can be overridden by subclasses to query the LDAP server in a more complex way. + * + * @param token the authentication token provided by the user. + * @param ldapContextFactory the factory used to build connections to the LDAP server. + * @return an {@link AuthenticationInfo} instance containing information retrieved from LDAP. + * @throws NamingException if any LDAP errors occur during the search. + */ + protected AuthenticationInfo queryForAuthenticationInfo( + AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException { + + UsernamePasswordToken upToken = (UsernamePasswordToken) token; + + // Binds using the username and password provided by the user. + LdapContext ctx = null; + try { + String userPrincipalName = upToken.getUsername(); + if (userPrincipalName == null) { + return null; + } + if (this.principalSuffix != null) { + userPrincipalName = upToken.getUsername() + this.principalSuffix; + } + ctx = ldapContextFactory.getLdapContext( + userPrincipalName, upToken.getPassword()); + } finally { + LdapUtils.closeContext(ctx); + } + + return buildAuthenticationInfo(upToken.getUsername(), upToken.getPassword()); + } + + protected AuthenticationInfo buildAuthenticationInfo(String username, char[] password) { + return new SimpleAuthenticationInfo(username, password, getName()); + } + + + /** + * Builds an {@link org.apache.shiro.authz.AuthorizationInfo} object by querying the active + * directory LDAP context for the groups that a user is a member of. The groups are then + * translated to role names by using the configured {@link #groupRolesMap}. + * <p/> + * This implementation expects the <tt>principal</tt> argument to be a String username. + * <p/> + * Subclasses can override this method to determine authorization data (roles, permissions, etc) + * in a more complex way. Note that this default implementation does not support permissions, + * only roles. + * + * @param principals the principal of the Subject whose account is being retrieved. + * @param ldapContextFactory the factory used to create LDAP connections. + * @return the AuthorizationInfo for the given Subject principal. + * @throws NamingException if an error occurs when searching the LDAP server. + */ + protected AuthorizationInfo queryForAuthorizationInfo( + PrincipalCollection principals, + LdapContextFactory ldapContextFactory) throws NamingException { + + String username = (String) getAvailablePrincipal(principals); + + // Perform context search + LdapContext ldapContext = ldapContextFactory.getSystemLdapContext(); + + Set<String> roleNames; + + try { + roleNames = getRoleNamesForUser(username, ldapContext); + } finally { + LdapUtils.closeContext(ldapContext); + } + + return buildAuthorizationInfo(roleNames); + } + + protected AuthorizationInfo buildAuthorizationInfo(Set<String> roleNames) { + return new SimpleAuthorizationInfo(roleNames); + } + + private Set<String> getRoleNamesForUser(String username, LdapContext ldapContext) + throws NamingException { + Set<String> roleNames = new LinkedHashSet<>(); + + SearchControls searchCtls = new SearchControls(); + searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); + String userPrincipalName = username; + if (principalSuffix != null) { + userPrincipalName += principalSuffix; + } + + String searchFilter = "(&(objectClass=*)(userPrincipalName=" + userPrincipalName + "))"; + Object[] searchArguments = new Object[]{userPrincipalName}; + + NamingEnumeration answer = ldapContext.search(searchBase, searchFilter, searchArguments, + searchCtls); + + while (answer.hasMoreElements()) { + SearchResult sr = (SearchResult) answer.next(); + + if (log.isDebugEnabled()) { + log.debug("Retrieving group names for user [" + sr.getName() + "]"); + } + + Attributes attrs = sr.getAttributes(); + + if (attrs != null) { + NamingEnumeration ae = attrs.getAll(); + while (ae.hasMore()) { + Attribute attr = (Attribute) ae.next(); + + if (attr.getID().equals("memberOf")) { + + Collection<String> groupNames = LdapUtils.getAllAttributeValues(attr); + + if (log.isDebugEnabled()) { + log.debug("Groups found for user [" + username + "]: " + groupNames); + } + + Collection<String> rolesForGroups = getRoleNamesForGroups(groupNames); + roleNames.addAll(rolesForGroups); + } + } + } + } + return roleNames; + } + + /** + * This method is called by the default implementation to translate Active Directory group names + * to role names. This implementation uses the {@link #groupRolesMap} to map group names to role + * names. + * + * @param groupNames the group names that apply to the current user. + * @return a collection of roles that are implied by the given role names. + */ + protected Collection<String> getRoleNamesForGroups(Collection<String> groupNames) { + Set<String> roleNames = new HashSet<String>(groupNames.size()); + + if (groupRolesMap != null) { + for (String groupName : groupNames) { + String strRoleNames = groupRolesMap.get(groupName); + if (strRoleNames != null) { + for (String roleName : strRoleNames.split(ROLE_NAMES_DELIMETER)) { + + if (log.isDebugEnabled()) { + log.debug("User is member of group [" + groupName + "] so adding role [" + + roleName + "]"); + } + + roleNames.add(roleName); + + } + } + } + } + return roleNames; + } + +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/24922e10/zeppelin-server/src/main/java/org/apache/zeppelin/server/LdapGroupRealm.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/LdapGroupRealm.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/LdapGroupRealm.java new file mode 100644 index 0000000..a718c77 --- /dev/null +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/LdapGroupRealm.java @@ -0,0 +1,94 @@ +/* + * 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.server; + +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.realm.ldap.JndiLdapRealm; +import org.apache.shiro.realm.ldap.LdapContextFactory; +import org.apache.shiro.subject.PrincipalCollection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.LdapContext; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + + +/** + * Created for org.apache.zeppelin.server on 09/06/16. + */ +public class LdapGroupRealm extends JndiLdapRealm { + private static final Logger LOG = LoggerFactory.getLogger(LdapGroupRealm.class); + + public AuthorizationInfo queryForAuthorizationInfo( + PrincipalCollection principals, + LdapContextFactory ldapContextFactory) throws NamingException { + String username = (String) getAvailablePrincipal(principals); + LdapContext ldapContext = ldapContextFactory.getSystemLdapContext(); + Set<String> roleNames = getRoleNamesForUser(username, ldapContext, getUserDnTemplate()); + return new SimpleAuthorizationInfo(roleNames); + } + + + public Set<String> getRoleNamesForUser(String username, + LdapContext ldapContext, + String userDnTemplate) throws NamingException { + try { + Set<String> roleNames = new LinkedHashSet<String>(); + + SearchControls searchCtls = new SearchControls(); + searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + String searchFilter = "(&(objectClass=groupOfNames)(member=" + userDnTemplate + "))"; + Object[] searchArguments = new Object[]{username}; + + NamingEnumeration<?> answer = ldapContext.search( + String.valueOf(ldapContext.getEnvironment().get("ldap.searchBase")), + searchFilter, + searchArguments, + searchCtls); + + while (answer.hasMoreElements()) { + SearchResult sr = (SearchResult) answer.next(); + Attributes attrs = sr.getAttributes(); + if (attrs != null) { + NamingEnumeration<?> ae = attrs.getAll(); + while (ae.hasMore()) { + Attribute attr = (Attribute) ae.next(); + if (attr.getID().equals("cn")) { + roleNames.add((String) attr.get()); + } + } + } + } + return roleNames; + + } catch (Exception e) { + LOG.error("Error", e); + } + + return new HashSet<>(); + } +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/24922e10/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java index dc190d1..20e8d5a 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java @@ -157,7 +157,7 @@ public class NotebookServer extends WebSocketServlet implements broadcastReloadedNoteList(); break; case GET_HOME_NOTE: - sendHomeNote(conn, userAndRoles, notebook); + sendHomeNote(conn, userAndRoles, notebook, messagereceived); break; case GET_NOTE: sendNote(conn, userAndRoles, notebook, messagereceived); @@ -451,13 +451,13 @@ public class NotebookServer extends WebSocketServlet implements broadcastAll(new Message(OP.NOTES_INFO).put("notes", notesInfo)); } - void permissionError(NotebookSocket conn, String op, Set<String> userAndRoles, + void permissionError(NotebookSocket conn, String op, + String userName, + Set<String> userAndRoles, Set<String> allowed) throws IOException { LOG.info("Cannot {}. Connection readers {}. Allowed readers {}", op, userAndRoles, allowed); - String userName = userAndRoles.iterator().next(); - conn.send(serializeMessage(new Message(OP.AUTH_INFO).put("info", "Insufficient privileges to " + op + " notebook.\n\n" + "Allowed users or roles: " + allowed.toString() + "\n\n" + @@ -481,7 +481,8 @@ public class NotebookServer extends WebSocketServlet implements NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization(); if (note != null) { if (!notebookAuthorization.isReader(noteId, userAndRoles)) { - permissionError(conn, "read", userAndRoles, notebookAuthorization.getReaders(noteId)); + permissionError(conn, "read", fromMessage.principal, userAndRoles, + notebookAuthorization.getReaders(noteId)); return; } addConnectionToNote(note.id(), conn); @@ -491,7 +492,7 @@ public class NotebookServer extends WebSocketServlet implements } private void sendHomeNote(NotebookSocket conn, HashSet<String> userAndRoles, - Notebook notebook) throws IOException { + Notebook notebook, Message fromMessage) throws IOException { String noteId = notebook.getConf().getString(ConfVars.ZEPPELIN_NOTEBOOK_HOMESCREEN); Note note = null; @@ -502,7 +503,8 @@ public class NotebookServer extends WebSocketServlet implements if (note != null) { NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization(); if (!notebookAuthorization.isReader(noteId, userAndRoles)) { - permissionError(conn, "read", userAndRoles, notebookAuthorization.getReaders(noteId)); + permissionError(conn, "read", fromMessage.principal, + userAndRoles, notebookAuthorization.getReaders(noteId)); return; } addConnectionToNote(note.id(), conn); @@ -530,7 +532,8 @@ public class NotebookServer extends WebSocketServlet implements NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization(); if (!notebookAuthorization.isWriter(noteId, userAndRoles)) { - permissionError(conn, "update", userAndRoles, notebookAuthorization.getWriters(noteId)); + permissionError(conn, "update", fromMessage.principal, + userAndRoles, notebookAuthorization.getWriters(noteId)); return; } @@ -593,7 +596,8 @@ public class NotebookServer extends WebSocketServlet implements Note note = notebook.getNote(noteId); NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization(); if (!notebookAuthorization.isOwner(noteId, userAndRoles)) { - permissionError(conn, "remove", userAndRoles, notebookAuthorization.getOwners(noteId)); + permissionError(conn, "remove", fromMessage.principal, + userAndRoles, notebookAuthorization.getOwners(noteId)); return; } @@ -617,7 +621,8 @@ public class NotebookServer extends WebSocketServlet implements final Note note = notebook.getNote(noteId); NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization(); if (!notebookAuthorization.isWriter(noteId, userAndRoles)) { - permissionError(conn, "write", userAndRoles, notebookAuthorization.getWriters(noteId)); + permissionError(conn, "write", fromMessage.principal, + userAndRoles, notebookAuthorization.getWriters(noteId)); return; } @@ -666,7 +671,8 @@ public class NotebookServer extends WebSocketServlet implements final Note note = notebook.getNote(noteId); NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization(); if (!notebookAuthorization.isWriter(noteId, userAndRoles)) { - permissionError(conn, "write", userAndRoles, notebookAuthorization.getWriters(noteId)); + permissionError(conn, "write", fromMessage.principal, + userAndRoles, notebookAuthorization.getWriters(noteId)); return; } @@ -688,7 +694,8 @@ public class NotebookServer extends WebSocketServlet implements final Note note = notebook.getNote(noteId); NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization(); if (!notebookAuthorization.isWriter(noteId, userAndRoles)) { - permissionError(conn, "write", userAndRoles, notebookAuthorization.getWriters(noteId)); + permissionError(conn, "write", fromMessage.principal, + userAndRoles, notebookAuthorization.getWriters(noteId)); return; } @@ -968,7 +975,8 @@ public class NotebookServer extends WebSocketServlet implements final Note note = notebook.getNote(noteId); NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization(); if (!notebookAuthorization.isWriter(noteId, userAndRoles)) { - permissionError(conn, "write", userAndRoles, notebookAuthorization.getWriters(noteId)); + permissionError(conn, "write", fromMessage.principal, + userAndRoles, notebookAuthorization.getWriters(noteId)); return; } @@ -985,7 +993,8 @@ public class NotebookServer extends WebSocketServlet implements final Note note = notebook.getNote(noteId); NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization(); if (!notebookAuthorization.isWriter(noteId, userAndRoles)) { - permissionError(conn, "write", userAndRoles, notebookAuthorization.getWriters(noteId)); + permissionError(conn, "write", fromMessage.principal, + userAndRoles, notebookAuthorization.getWriters(noteId)); return; } @@ -1005,7 +1014,8 @@ public class NotebookServer extends WebSocketServlet implements final Note note = notebook.getNote(noteId); NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization(); if (!notebookAuthorization.isWriter(noteId, userAndRoles)) { - permissionError(conn, "write", userAndRoles, notebookAuthorization.getWriters(noteId)); + permissionError(conn, "write", fromMessage.principal, + userAndRoles, notebookAuthorization.getWriters(noteId)); return; } @@ -1024,7 +1034,8 @@ public class NotebookServer extends WebSocketServlet implements final Note note = notebook.getNote(noteId); NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization(); if (!notebookAuthorization.isWriter(noteId, userAndRoles)) { - permissionError(conn, "write", userAndRoles, notebookAuthorization.getWriters(noteId)); + permissionError(conn, "write", fromMessage.principal, + userAndRoles, notebookAuthorization.getWriters(noteId)); return; } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/24922e10/zeppelin-server/src/main/java/org/apache/zeppelin/utils/SecurityUtils.java ---------------------------------------------------------------------- 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 e7e39f2..4de4573 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 @@ -16,15 +16,18 @@ */ package org.apache.zeppelin.utils; +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 java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.HashSet; +import java.util.*; /** * Tools for securing Zeppelin @@ -33,7 +36,7 @@ public class SecurityUtils { public static Boolean isValidOrigin(String sourceHost, ZeppelinConfiguration conf) throws UnknownHostException, URISyntaxException { - if (sourceHost == null || sourceHost.isEmpty()){ + if (sourceHost == null || sourceHost.isEmpty()) { return false; } String sourceUriHost = new URI(sourceHost).getHost(); @@ -43,13 +46,14 @@ public class SecurityUtils { String currentHost = InetAddress.getLocalHost().getHostName().toLowerCase(); return conf.getAllowedOrigins().contains("*") || - currentHost.equals(sourceUriHost) || - "localhost".equals(sourceUriHost) || - conf.getAllowedOrigins().contains(sourceHost); + currentHost.equals(sourceUriHost) || + "localhost".equals(sourceUriHost) || + conf.getAllowedOrigins().contains(sourceHost); } /** * Return the authenticated user if any otherwise returns "anonymous" + * * @return shiro principal */ public static String getPrincipal() { @@ -58,26 +62,49 @@ public class SecurityUtils { String principal; if (subject.isAuthenticated()) { principal = subject.getPrincipal().toString(); - } - else { + } else { principal = "anonymous"; } return principal; } + public static Collection getRealmsList() { + DefaultWebSecurityManager defaultWebSecurityManager; + String key = ThreadContext.SECURITY_MANAGER_KEY; + defaultWebSecurityManager = (DefaultWebSecurityManager) ThreadContext.get(key); + Collection<Realm> realms = defaultWebSecurityManager.getRealms(); + return realms; + } + /** * Return the roles associated with the authenticated user if any otherwise returns empty set * TODO(prasadwagle) Find correct way to get user roles (see SHIRO-492) + * * @return shiro roles */ public static HashSet<String> getRoles() { Subject subject = org.apache.shiro.SecurityUtils.getSubject(); HashSet<String> roles = new HashSet<>(); + Map allRoles = null; if (subject.isAuthenticated()) { - for (String role : Arrays.asList("role1", "role2", "role3")) { - if (subject.hasRole(role)) { - roles.add(role); + Collection realmsList = SecurityUtils.getRealmsList(); + for (Iterator<Realm> iterator = realmsList.iterator(); iterator.hasNext(); ) { + Realm realm = iterator.next(); + String name = realm.getName(); + if (name.equals("iniRealm")) { + allRoles = ((IniRealm) realm).getIni().get("roles"); + 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()); + } } } } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/24922e10/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java index a86d08b..3e56747 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java @@ -38,7 +38,7 @@ import static org.openqa.selenium.Keys.ENTER; import static org.openqa.selenium.Keys.SHIFT; abstract public class AbstractZeppelinIT { - protected WebDriver driver; + protected static WebDriver driver; protected final static Logger LOG = LoggerFactory.getLogger(AbstractZeppelinIT.class); protected static final long MAX_IMPLICIT_WAIT = 30; @@ -114,7 +114,7 @@ abstract public class AbstractZeppelinIT { }); } - protected boolean endToEndTestEnabled() { + protected static boolean endToEndTestEnabled() { return null != System.getenv("TEST_SELENIUM"); } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/24922e10/zeppelin-server/src/test/java/org/apache/zeppelin/WebDriverManager.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/WebDriverManager.java b/zeppelin-server/src/test/java/org/apache/zeppelin/WebDriverManager.java index 4f0f394..49d6f1e 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/WebDriverManager.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/WebDriverManager.java @@ -125,7 +125,8 @@ public class WebDriverManager { (new WebDriverWait(driver, 5)).until(new ExpectedCondition<Boolean>() { @Override public Boolean apply(WebDriver d) { - return d.findElement(By.partialLinkText("Create new note")) + return d.findElement(By.xpath( + "//div[contains(@class, 'navbar-collapse')]//li//a[contains(.,'Connected')]")) .isDisplayed(); } }); http://git-wip-us.apache.org/repos/asf/zeppelin/blob/24922e10/zeppelin-server/src/test/java/org/apache/zeppelin/ZeppelinITUtils.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/ZeppelinITUtils.java b/zeppelin-server/src/test/java/org/apache/zeppelin/ZeppelinITUtils.java index 9800df6..46ffbe7 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/ZeppelinITUtils.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/ZeppelinITUtils.java @@ -39,4 +39,11 @@ public class ZeppelinITUtils { LOG.info("Finished."); } } + + public static void restartZeppelin() { + CommandExecutor.executeCommandLocalHost("../bin/zeppelin-daemon.sh restart", + false, ProcessData.Types_Of_Data.OUTPUT); + //wait for server to start. + sleep(5000, false); + } } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/24922e10/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java new file mode 100644 index 0000000..3b1088e --- /dev/null +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java @@ -0,0 +1,209 @@ +/* + * 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.integration; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.zeppelin.AbstractZeppelinIT; +import org.apache.zeppelin.WebDriverManager; +import org.apache.zeppelin.ZeppelinITUtils; +import org.apache.zeppelin.conf.ZeppelinConfiguration; +import org.hamcrest.CoreMatchers; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; +import org.openqa.selenium.WebElement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.List; + + +/** + * Created for org.apache.zeppelin.integration on 13/06/16. + */ +public class AuthenticationIT extends AbstractZeppelinIT { + private static final Logger LOG = LoggerFactory.getLogger(AuthenticationIT.class); + + @Rule + public ErrorCollector collector = new ErrorCollector(); + + static String authShiro = "[users]\n" + + "admin = password1, admin\n" + + "finance1 = finance1, finance\n" + + "finance2 = finance2, finance\n" + + "hr1 = hr1, hr\n" + + "hr2 = hr2, hr\n" + + "[main]\n" + + "sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager\n" + + "securityManager.sessionManager = $sessionManager\n" + + "securityManager.sessionManager.globalSessionTimeout = 86400000\n" + + "shiro.loginUrl = /api/login\n" + + "[roles]\n" + + "admin = *\n" + + "hr = *\n" + + "finance = *\n" + + "[urls]\n" + + "/api/version = anon\n" + + "/** = authc"; + + static String originalShiro = ""; + + + @BeforeClass + public static void startUp() { + if (!endToEndTestEnabled()) { + return; + } + + try { + ZeppelinConfiguration conf = ZeppelinConfiguration.create(); + File file = new File(conf.getShiroPath()); + originalShiro = StringUtils.join(FileUtils.readLines(file, "UTF-8"), "\n"); + FileUtils.write(file, authShiro, "UTF-8"); + } catch (IOException e) { + LOG.error("Error in AuthenticationIT startUp::", e); + } + ZeppelinITUtils.restartZeppelin(); + driver = WebDriverManager.getWebDriver(); + } + + + @AfterClass + public static void tearDown() { + if (!endToEndTestEnabled()) { + return; + } + try { + ZeppelinConfiguration conf = ZeppelinConfiguration.create(); + File file = new File(conf.getShiroPath()); + FileUtils.write(file, originalShiro, "UTF-8"); + } catch (IOException e) { + LOG.error("Error in AuthenticationIT tearDown::", e); + } + ZeppelinITUtils.restartZeppelin(); + driver.quit(); + } + + private void authenticationUser(String userName, String password) { + pollingWait(By.xpath( + "//div[contains(@class, 'navbar-collapse')]//li//button[contains(.,'Login')]"), + MAX_BROWSER_TIMEOUT_SEC).click(); + sleep(1000, false); + pollingWait(By.xpath("//*[@id='userName']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys(userName); + pollingWait(By.xpath("//*[@id='password']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys(password); + pollingWait(By.xpath("//*[@id='NoteImportCtrl']//button[contains(.,'Login')]"), + MAX_BROWSER_TIMEOUT_SEC).click(); + sleep(1000, false); + } + + private void logoutUser(String userName) { + sleep(500, false); + driver.findElement(By.xpath("//div[contains(@class, 'navbar-collapse')]//li[contains(.,'" + + userName + "')]")).click(); + sleep(500, false); + driver.findElement(By.xpath("//div[contains(@class, 'navbar-collapse')]//li[contains(.,'" + + userName + "')]//a[@ng-click='logout()']")).click(); + sleep(5000, false); + } + + // @Test + public void testSimpleAuthentication() throws Exception { + if (!endToEndTestEnabled()) { + return; + } + try { + AuthenticationIT authenticationIT = new AuthenticationIT(); + authenticationIT.authenticationUser("admin", "password1"); + + collector.checkThat("Check is user logged in", true, + CoreMatchers.equalTo(driver.findElement(By.partialLinkText("Create new note")) + .isDisplayed())); + + authenticationIT.logoutUser("admin"); + } catch (Exception e) { + handleException("Exception in ParagraphActionsIT while testCreateNewButton ", e); + } + } + + @Test + public void testGroupPermission() throws Exception { + if (!endToEndTestEnabled()) { + return; + } + try { + AuthenticationIT authenticationIT = new AuthenticationIT(); + authenticationIT.authenticationUser("finance1", "finance1"); + createNewNote(); + + String noteId = driver.getCurrentUrl().substring(driver.getCurrentUrl().lastIndexOf("/") + 1); + + pollingWait(By.xpath("//button[@tooltip='Note permissions']"), + MAX_BROWSER_TIMEOUT_SEC).sendKeys(Keys.ENTER); + pollingWait(By.xpath("//input[@ng-model='permissions.owners']"), MAX_BROWSER_TIMEOUT_SEC) + .sendKeys("finance"); + pollingWait(By.xpath("//input[@ng-model='permissions.readers']"), MAX_BROWSER_TIMEOUT_SEC) + .sendKeys("finance"); + pollingWait(By.xpath("//input[@ng-model='permissions.writers']"), MAX_BROWSER_TIMEOUT_SEC) + .sendKeys("finance"); + pollingWait(By.xpath("//button[@ng-click='savePermissions()']"), MAX_BROWSER_TIMEOUT_SEC) + .sendKeys(Keys.ENTER); + + pollingWait(By.xpath("//div[@class='modal-dialog'][contains(.,'Permissions Saved ')]" + + "//div[@class='modal-footer']//button[contains(.,'OK')]"), + MAX_BROWSER_TIMEOUT_SEC).click(); + authenticationIT.logoutUser("finance1"); + + authenticationIT.authenticationUser("hr1", "hr1"); + pollingWait(By.xpath("//*[@id='notebook-names']//a[contains(@href, '" + noteId + "')]"), + MAX_BROWSER_TIMEOUT_SEC).click(); + + List<WebElement> privilegesModal = driver.findElements( + By.xpath("//div[@class='modal-content']//div[@class='bootstrap-dialog-header']" + + "//div[contains(.,'Insufficient privileges')]")); + collector.checkThat("Check is user has permission to view this notebook", 1, + CoreMatchers.equalTo(privilegesModal.size())); + driver.findElement( + By.xpath("//div[@class='modal-content'][contains(.,'Insufficient privileges')]" + + "//div[@class='modal-footer']//button[2]")).click(); + authenticationIT.logoutUser("hr1"); + + authenticationIT.authenticationUser("finance2", "finance2"); + pollingWait(By.xpath("//*[@id='notebook-names']//a[contains(@href, '" + noteId + "')]"), + MAX_BROWSER_TIMEOUT_SEC).click(); + + privilegesModal = driver.findElements( + By.xpath("//div[@class='modal-content']//div[@class='bootstrap-dialog-header']" + + "//div[contains(.,'Insufficient privileges')]")); + collector.checkThat("Check is user has permission to view this notebook", 0, + CoreMatchers.equalTo(privilegesModal.size())); + deleteTestNotebook(driver); + authenticationIT.logoutUser("finance2"); + + + } catch (Exception e) { + handleException("Exception in ParagraphActionsIT while testGroupPermission ", e); + } + } + +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/24922e10/zeppelin-web/src/app/notebook/notebook-actionBar.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/notebook/notebook-actionBar.html b/zeppelin-web/src/app/notebook/notebook-actionBar.html index fc1932f..28dd84e 100644 --- a/zeppelin-web/src/app/notebook/notebook-actionBar.html +++ b/zeppelin-web/src/app/notebook/notebook-actionBar.html @@ -151,21 +151,26 @@ limitations under the License. </span> <div class="pull-right" style="margin-top:15px; margin-right:15px; font-size:15px;"> - <span style="position:relative; top:3px; margin-right:4px; cursor:pointer" + <span> + <button type="button" + class="btn btn-default btn-xs" data-toggle="modal" data-target="#shortcutModal" tooltip-placement="bottom" tooltip="List of shortcut"> - <i class="icon-question"></i> - </span> - <span style="position:relative; top:2px; margin-right:4px; cursor:pointer;" + <i class="icon-question"></i> + </button> + <button type="button" + class="btn btn-default btn-xs" ng-click="toggleSetting()" tooltip-placement="bottom" tooltip="Interpreter binding"> - <i class="fa fa-cog" ng-style="{color: showSetting ? '#3071A9' : 'black' }"></i> - </span> - <span style="position:relative; top:2px; margin-right:4px; cursor:pointer;" + <i class="fa fa-cog" ng-style="{color: showSetting ? '#3071A9' : 'black' }"></i> + </button> + <button type="button" + class="btn btn-default btn-xs" ng-click="togglePermissions()" tooltip-placement="bottom" tooltip="Note permissions"> - <i class="fa fa-lock" ng-style="{color: showPermissions ? '#3071A9' : 'black' }"></i> + <i class="fa fa-lock" ng-style="{color: showPermissions ? '#3071A9' : 'black' }"></i> + </button> </span> <span class="btn-group">
