You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by pr...@apache.org on 2016/06/17 09:19:11 UTC
zeppelin git commit: [Zeppelin 946] Permissions not honoring group
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 <pr...@gmail.com>
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 <pr...@gmail.com>
Authored: Fri Jun 17 07:35:21 2016 +0530
Committer: Prabhjyot Singh <pr...@gmail.com>
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">