Merge branch 'master' into extends-zrun-remote-transaction

This commit is contained in:
CloverHearts 2016-11-21 11:27:30 +09:00
commit f9661c843b
30 changed files with 1302 additions and 102 deletions

View file

@ -18,9 +18,11 @@ language: java
sudo: false
cache:
apt: true
directories:
- .spark-dist
- ${HOME}/.m2/repository/.cache/maven-download-plugin
- ${HOME}/.m2
- ${HOME}/R
- .node_modules
addons:
@ -81,7 +83,7 @@ before_script:
- tail conf/zeppelin-env.sh
script:
- mvn $TEST_FLAG $PROFILE -B $TEST_PROJECTS
- mvn -Dorg.slf4j.simpleLogger.defaultLogLevel=warn $TEST_FLAG $PROFILE -B $TEST_PROJECTS
- rm -rf .node_modules; cp -r zeppelin-web/node_modules .node_modules
after_success:

View file

@ -38,6 +38,7 @@
# export ZEPPELIN_INTERPRETER_LOCALREPO # Local repository for interpreter's additional dependency loading
# export ZEPPELIN_NOTEBOOK_STORAGE # Refers to pluggable notebook storage class, can have two classes simultaneously with a sync between them (e.g. local and remote).
# export ZEPPELIN_NOTEBOOK_ONE_WAY_SYNC # If there are multiple notebook storages, should we treat the first one as the only source of truth?
# export ZEPPELIN_NOTEBOOK_PUBLIC # Make notebook public by default when created, private otherwise
#### Spark interpreter configuration ####

View file

@ -277,6 +277,12 @@
<description>Anonymous user allowed by default</description>
</property>
<property>
<name>zeppelin.notebook.public</name>
<value>true</value>
<description>Make notebook public by default when created, private otherwise</description>
</property>
<property>
<name>zeppelin.websocket.max.text.message.size</name>
<value>1024000</value>
@ -284,4 +290,3 @@
</property>
</configuration>

View file

@ -87,7 +87,7 @@ Congratulations, you have successfully installed Apache Zeppelin! Here are few s
* For an in-depth overview, head to [Explore Apache Zeppelin UI](../quickstart/explorezeppelinui.html).
* And then, try run [tutorial](http://localhost:8080/#/notebook/2A94M5J1Z) notebook in your Zeppelin.
* And see how to change [configurations](#apache-zeppelin-configuration) like port number, etc.
#### Zeppelin with Apache Spark ...
* To know more about deep integration with [Apache Spark](http://spark.apache.org/), check [Spark Interpreter](../interpreter/spark.html).
@ -306,6 +306,12 @@ You can configure Apache Zeppelin with either **environment variables** in `conf
<td>false</td>
<td>If there are multiple notebook storage locations, should we treat the first one as the only source of truth?</td>
</tr>
<tr>
<td>ZEPPELIN_NOTEBOOK_PUBLIC</td>
<td>zeppelin.notebook.public</td>
<td>true</td>
<td>Make notebook public (set only `owners`) by default when created/imported. If set to `false` will add `user` to `readers` and `writers` as well, making it private and invisible to other users unless permissions are granted.</td>
</tr>
<tr>
<td>ZEPPELIN_INTERPRETERS</td>
<td>zeppelin.interpreters</td>
@ -377,4 +383,4 @@ exec bin/zeppelin-daemon.sh upstart
## Building from Source
If you want to build from source instead of using binary package, follow the instructions [here](./build.html).
If you want to build from source instead of using binary package, follow the instructions [here](./build.html).

View file

@ -228,8 +228,7 @@ Here are few examples:
```
### 3. Dynamic Dependency Loading via %spark.dep interpreter
> Note: `%spark.dep` interpreter is deprecated since v0.6.0.
`%spark.dep` interpreter loads libraries to `%spark` and `%spark.pyspark` but not to `%spark.sql` interpreter. So we recommend you to use the first option instead.
> Note: `%spark.dep` interpreter loads libraries to `%spark` and `%spark.pyspark` but not to `%spark.sql` interpreter. So we recommend you to use the first option instead.
When your code requires external library, instead of doing download/copy/restart Zeppelin, you can easily do following jobs using `%spark.dep` interpreter.

View file

@ -81,7 +81,7 @@ println(
<i style="font-size: 15px;" class="icon-notebook"></i> Create new note</a></h5>
<ul style="list-style-type: none;">
<li ng-repeat="note in home.notes.list track by $index"><i style="font-size: 10px;" class="icon-doc"></i>
<a style="text-decoration: none;" href="#/notebook/{{note.id}}">{{noteName(note)}}</a>
<a style="text-decoration: none;" href="#/notebook/{{"{{note.id"}}}}>{{"{{noteName(note)"}}}}</a>
</li>
</ul>
</div>

View file

@ -178,6 +178,65 @@ The role of registered interpreters, settings and interpreters group are describ
</td>
</tr>
</table>
<br/>
### Get a registered interpreter setting by the setting id
<table class="table-configuration">
<col width="200">
<tr>
<td>Description</td>
<td>This ```GET``` method returns a registered interpreter setting on the server.</td>
</tr>
<tr>
<td>URL</td>
<td>```http://[zeppelin-server]:[zeppelin-port]/api/interpreter/setting/[setting ID]```</td>
</tr>
<tr>
<td>Success code</td>
<td>200</td>
</tr>
<tr>
<td>Fail code</td>
<td>
400 if such interpreter setting id does not exist <br/>
500 for any other errors
</td>
</tr>
<tr>
<td>Sample JSON response</td>
<td>
<pre>
{
"status": "OK",
"message": "",
"body": {
"id": "2AYW25ANY",
"name": "Markdown setting name",
"group": "md",
"properties": {
"propname": "propvalue"
},
"interpreterGroup": [
{
"class": "org.apache.zeppelin.markdown.Markdown",
"name": "md"
}
],
"dependencies": [
{
"groupArtifactVersion": "groupId:artifactId:version",
"exclusions": [
"groupId:artifactId"
]
}
]
}
}
</pre>
</td>
</tr>
</table>
<br/>
### Create a new interpreter setting

View file

@ -42,8 +42,6 @@ import org.sonatype.aether.util.artifact.JavaScopes;
import org.sonatype.aether.util.filter.DependencyFilterUtils;
import org.sonatype.aether.util.filter.PatternExclusionsDependencyFilter;
import scala.Console;
/**
*
@ -66,8 +64,6 @@ public class SparkDependencyContext {
}
public Dependency load(String lib) {
Console.println("DepInterpreter(%dep) deprecated. "
+ "Load dependency through GUI interpreter menu instead.");
Dependency dep = new Dependency(lib);
if (dependencies.contains(dep)) {
@ -78,16 +74,12 @@ public class SparkDependencyContext {
}
public Repository addRepo(String name) {
Console.println("DepInterpreter(%dep) deprecated. "
+ "Add repository through GUI interpreter menu instead.");
Repository rep = new Repository(name);
repositories.add(rep);
return rep;
}
public void reset() {
Console.println("DepInterpreter(%dep) deprecated. "
+ "Remove dependencies and repositories through GUI interpreter menu instead.");
dependencies = new LinkedList<>();
repositories = new LinkedList<>();

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zeppelin.server;
package org.apache.zeppelin.realm;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zeppelin.server;
package org.apache.zeppelin.realm;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;

View file

@ -0,0 +1,842 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.zeppelin.realm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.DefaultHashService;
import org.apache.shiro.crypto.hash.Hash;
import org.apache.shiro.crypto.hash.HashRequest;
import org.apache.shiro.crypto.hash.HashService;
import org.apache.shiro.realm.ldap.JndiLdapRealm;
import org.apache.shiro.realm.ldap.LdapContextFactory;
import org.apache.shiro.realm.ldap.LdapUtils;
import org.apache.shiro.subject.MutablePrincipalCollection;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.StringUtils;
import org.mortbay.log.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.PartialResultException;
import javax.naming.SizeLimitExceededException;
import javax.naming.directory.Attribute;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.PagedResultsControl;
/**
* Implementation of {@link org.apache.shiro.realm.ldap.JndiLdapRealm} that also
* returns each user's groups. This implementation is heavily based on
* org.apache.isis.security.shiro.IsisLdapRealm.
*
* <p>This implementation saves looked up ldap groups in Shiro Session to make them
* easy to be looked up outside of this object
*
* <p>Sample config for <tt>shiro.ini</tt>:
*
* <p>[main]
* ldapRealm=org.apache.zeppelin.realm.LdapRealm
* ldapRealm.contextFactory=$ldapGroupContextFactory
* ldapRealm.contextFactory.authenticationMechanism=simple
* ldapRealm.contextFactory.url=ldap://localhost:33389
* ldapRealm.userDnTemplate=uid={0},ou=people,dc=hadoop,dc=apache,dc=org
* # Ability to set ldap paging Size if needed default is 100
* ldapRealm.pagingSize = 200
* ldapRealm.authorizationEnabled=true
* ldapRealm.contextFactory.systemAuthenticationMechanism=simple
* ldapRealm.searchBase=dc=hadoop,dc=apache,dc=org
* ldapRealm.userSearchBase = dc=hadoop,dc=apache,dc=org
* ldapRealm.groupSearchBase = ou=groups,dc=hadoop,dc=apache,dc=org
* ldapRealm.groupObjectClass=groupofnames
* # Allow userSearchAttribute to be customized
* ldapRealm.userSearchAttributeName = sAMAccountName
* ldapRealm.memberAttribute=member
* # force usernames returned from ldap to lowercase useful for AD
* ldapRealm.userLowerCase = true
* # ability set searchScopes subtree (default), one, base
* ldapRealm.userSearchScope = subtree;
* ldapRealm.groupSearchScope = subtree;
* ldapRealm.memberAttributeValueTemplate=cn={0},ou=people,dc=hadoop,dc=apache,
* dc=org
* ldapRealm.contextFactory.systemUsername=uid=guest,ou=people,dc=hadoop,dc=
* apache,dc=org
* ldapRealm.contextFactory.systemPassword=S{ALIAS=ldcSystemPassword} [urls]
* **=authcBasic
*
* <p># optional mapping from physical groups to logical application roles
* ldapRealm.rolesByGroup = \ LDN_USERS: user_role,\ NYK_USERS: user_role,\
* HKG_USERS: user_role,\ GLOBAL_ADMIN: admin_role,\ DEMOS: self-install_role
*
* <p>ldapRealm.permissionsByRole=\ user_role = *:ToDoItemsJdo:*:*,\
* *:ToDoItem:*:*; \ self-install_role = *:ToDoItemsFixturesService:install:* ;
* \ admin_role = *
*
* <p>securityManager.realms = $ldapRealm
*
*/
public class LdapRealm extends JndiLdapRealm {
private static final SearchControls SUBTREE_SCOPE = new SearchControls();
private static final SearchControls ONELEVEL_SCOPE = new SearchControls();
private static final SearchControls OBJECT_SCOPE = new SearchControls();
private static final String SUBJECT_USER_ROLES = "subject.userRoles";
private static final String SUBJECT_USER_GROUPS = "subject.userGroups";
private static final String MEMBER_URL = "memberUrl";
private static final String POSIX_GROUP = "posixGroup";
private static Pattern TEMPLATE_PATTERN = Pattern.compile("\\{(\\d+?)\\}");
private static String DEFAULT_PRINCIPAL_REGEX = "(.*)";
private static final String MEMBER_SUBSTITUTION_TOKEN = "{0}";
private static final String HASHING_ALGORITHM = "SHA-1";
private static final Logger log = LoggerFactory.getLogger(LdapRealm.class);
static {
SUBTREE_SCOPE.setSearchScope(SearchControls.SUBTREE_SCOPE);
ONELEVEL_SCOPE.setSearchScope(SearchControls.ONELEVEL_SCOPE);
OBJECT_SCOPE.setSearchScope(SearchControls.OBJECT_SCOPE);
}
private String searchBase;
private String userSearchBase;
private int pagingSize = 100;
private boolean userLowerCase;
private String principalRegex = DEFAULT_PRINCIPAL_REGEX;
private Pattern principalPattern = Pattern.compile(DEFAULT_PRINCIPAL_REGEX);
private String userDnTemplate = "{0}";
private String userSearchFilter = null;
private String userSearchAttributeTemplate = "{0}";
private String userSearchScope = "subtree";
private String groupSearchScope = "subtree";
private String groupSearchBase;
private String groupObjectClass = "groupOfNames";
// typical value: member, uniqueMember, meberUrl
private String memberAttribute = "member";
private String groupIdAttribute = "cn";
private String memberAttributeValuePrefix = "uid={0}";
private String memberAttributeValueSuffix = "";
private final Map<String, String> rolesByGroup = new LinkedHashMap<String, String>();
private final Map<String, List<String>> permissionsByRole =
new LinkedHashMap<String, List<String>>();
private boolean authorizationEnabled;
private String userSearchAttributeName;
private String userObjectClass = "person";
private HashService hashService = new DefaultHashService();
public LdapRealm() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(HASHING_ALGORITHM);
setCredentialsMatcher(credentialsMatcher);
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws org.apache.shiro.authc.AuthenticationException {
try {
return super.doGetAuthenticationInfo(token);
} catch (org.apache.shiro.authc.AuthenticationException ae) {
throw ae;
}
}
/**
* Get groups from LDAP.
*
* @param principals
* the principals of the Subject whose AuthenticationInfo should
* be queried from the LDAP server.
* @param ldapContextFactory
* factory used to retrieve LDAP connections.
* @return an {@link AuthorizationInfo} instance containing information
* retrieved from the LDAP server.
* @throws NamingException
* if any LDAP errors occur during the search.
*/
@Override
protected AuthorizationInfo queryForAuthorizationInfo(final PrincipalCollection principals,
final LdapContextFactory ldapContextFactory) throws NamingException {
if (!isAuthorizationEnabled()) {
return null;
}
final Set<String> roleNames = getRoles(principals, ldapContextFactory);
if (log.isDebugEnabled()) {
log.debug("RolesNames Authorization: " + roleNames);
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roleNames);
Set<String> stringPermissions = permsFor(roleNames);
simpleAuthorizationInfo.setStringPermissions(stringPermissions);
return simpleAuthorizationInfo;
}
private Set<String> getRoles(PrincipalCollection principals,
final LdapContextFactory ldapContextFactory)
throws NamingException {
final String username = (String) getAvailablePrincipal(principals);
LdapContext systemLdapCtx = null;
try {
systemLdapCtx = ldapContextFactory.getSystemLdapContext();
return rolesFor(principals, username, systemLdapCtx, ldapContextFactory);
} catch (AuthenticationException ae) {
ae.printStackTrace();
return Collections.emptySet();
} finally {
LdapUtils.closeContext(systemLdapCtx);
}
}
private Set<String> rolesFor(PrincipalCollection principals,
String userNameIn, final LdapContext ldapCtx,
final LdapContextFactory ldapContextFactory) throws NamingException {
final Set<String> roleNames = new HashSet<>();
final Set<String> groupNames = new HashSet<>();
final String userName;
if (getUserLowerCase()) {
log.debug("userLowerCase true");
userName = userNameIn.toLowerCase();
} else {
userName = userNameIn;
}
String userDn;
if (userSearchAttributeName == null || userSearchAttributeName.isEmpty()) {
// memberAttributeValuePrefix and memberAttributeValueSuffix
// were computed from memberAttributeValueTemplate
userDn = memberAttributeValuePrefix + userName + memberAttributeValueSuffix;
} else {
userDn = getUserDn(userName);
}
// Activate paged results
int pageSize = getPagingSize();
if (log.isDebugEnabled()) {
log.debug("Ldap PagingSize: " + pageSize);
}
int numResults = 0;
byte[] cookie = null;
try {
ldapCtx.addToEnvironment(Context.REFERRAL, "ignore");
ldapCtx.setRequestControls(new Control[]{new PagedResultsControl(pageSize,
Control.NONCRITICAL)});
do {
// ldapsearch -h localhost -p 33389 -D
// uid=guest,ou=people,dc=hadoop,dc=apache,dc=org -w guest-password
// -b dc=hadoop,dc=apache,dc=org -s sub '(objectclass=*)'
NamingEnumeration<SearchResult> searchResultEnum = null;
SearchControls searchControls = getGroupSearchControls();
try {
searchResultEnum = ldapCtx.search(
getGroupSearchBase(),
"objectClass=" + groupObjectClass,
searchControls);
while (searchResultEnum != null && searchResultEnum.hasMore()) {
// searchResults contains all the groups in search scope
numResults++;
final SearchResult group = searchResultEnum.next();
addRoleIfMember(userDn, group, roleNames, groupNames, ldapContextFactory);
}
} catch (PartialResultException e) {
log.debug("Ignoring PartitalResultException");
} finally {
if (searchResultEnum != null) {
searchResultEnum.close();
}
}
// Re-activate paged results
ldapCtx.setRequestControls(new Control[]{new PagedResultsControl(pageSize,
cookie, Control.CRITICAL)});
} while (cookie != null);
} catch (SizeLimitExceededException e) {
log.info("Only retrieved first " + numResults +
" groups due to SizeLimitExceededException.");
} catch (IOException e) {
log.error("Unabled to setup paged results");
}
// save role names and group names in session so that they can be
// easily looked up outside of this object
SecurityUtils.getSubject().getSession().setAttribute(SUBJECT_USER_ROLES, roleNames);
SecurityUtils.getSubject().getSession().setAttribute(SUBJECT_USER_GROUPS, groupNames);
if (!groupNames.isEmpty() && (principals instanceof MutablePrincipalCollection)) {
((MutablePrincipalCollection) principals).addAll(groupNames, getName());
}
if (log.isDebugEnabled()) {
log.debug("User RoleNames: " + userName + "::" + roleNames);
}
return roleNames;
}
private void addRoleIfMember(final String userDn, final SearchResult group,
final Set<String> roleNames, final Set<String> groupNames,
final LdapContextFactory ldapContextFactory) throws NamingException {
NamingEnumeration<? extends Attribute> attributeEnum = null;
NamingEnumeration<?> ne = null;
try {
LdapName userLdapDn = new LdapName(userDn);
Attribute attribute = group.getAttributes().get(getGroupIdAttribute());
String groupName = attribute.get().toString();
attributeEnum = group.getAttributes().getAll();
while (attributeEnum.hasMore()) {
final Attribute attr = attributeEnum.next();
if (!memberAttribute.equalsIgnoreCase(attr.getID())) {
continue;
}
ne = attr.getAll();
while (ne.hasMore()) {
String attrValue = ne.next().toString();
if (memberAttribute.equalsIgnoreCase(MEMBER_URL)) {
boolean dynamicGroupMember = isUserMemberOfDynamicGroup(userLdapDn, attrValue,
ldapContextFactory);
if (dynamicGroupMember) {
groupNames.add(groupName);
String roleName = roleNameFor(groupName);
if (roleName != null) {
roleNames.add(roleName);
} else {
roleNames.add(groupName);
}
}
} else {
if (groupObjectClass.equalsIgnoreCase(POSIX_GROUP)) {
attrValue = memberAttributeValuePrefix + attrValue + memberAttributeValueSuffix;
}
if (userLdapDn.equals(new LdapName(attrValue))) {
groupNames.add(groupName);
String roleName = roleNameFor(groupName);
if (roleName != null) {
roleNames.add(roleName);
} else {
roleNames.add(groupName);
}
break;
}
}
}
}
} finally {
try {
if (attributeEnum != null) {
attributeEnum.close();
}
} finally {
if (ne != null) {
ne.close();
}
}
}
}
public Map<String, String> getListRoles() {
Map<String, String> groupToRoles = getRolesByGroup();
Map<String, String> roles = new HashMap<>();
for (Map.Entry<String, String> entry : groupToRoles.entrySet()){
roles.put(entry.getValue(), entry.getKey());
}
return roles;
}
private String roleNameFor(String groupName) {
return !rolesByGroup.isEmpty() ? rolesByGroup.get(groupName) : groupName;
}
private Set<String> permsFor(Set<String> roleNames) {
Set<String> perms = new LinkedHashSet<String>(); // preserve order
for (String role : roleNames) {
List<String> permsForRole = permissionsByRole.get(role);
if (log.isDebugEnabled()) {
log.debug("PermsForRole: " + role);
log.debug("PermByRole: " + permsForRole);
}
if (permsForRole != null) {
perms.addAll(permsForRole);
}
}
return perms;
}
public String getSearchBase() {
return searchBase;
}
public void setSearchBase(String searchBase) {
this.searchBase = searchBase;
}
public String getUserSearchBase() {
return (userSearchBase != null && !userSearchBase.isEmpty()) ? userSearchBase : searchBase;
}
public void setUserSearchBase(String userSearchBase) {
this.userSearchBase = userSearchBase;
}
public int getPagingSize() {
return pagingSize;
}
public void setPagingSize(int pagingSize) {
this.pagingSize = pagingSize;
}
public String getGroupSearchBase() {
return (groupSearchBase != null && !groupSearchBase.isEmpty()) ? groupSearchBase : searchBase;
}
public void setGroupSearchBase(String groupSearchBase) {
this.groupSearchBase = groupSearchBase;
}
public String getGroupObjectClass() {
return groupObjectClass;
}
public void setGroupObjectClass(String groupObjectClassAttribute) {
this.groupObjectClass = groupObjectClassAttribute;
}
public String getMemberAttribute() {
return memberAttribute;
}
public void setMemberAttribute(String memberAttribute) {
this.memberAttribute = memberAttribute;
}
public String getGroupIdAttribute() {
return groupIdAttribute;
}
public void setGroupIdAttribute(String groupIdAttribute) {
this.groupIdAttribute = groupIdAttribute;
}
/**
* Set Member Attribute Template for LDAP.
*
* @param template
* DN template to be used to query ldap.
* @throws IllegalArgumentException
* if template is empty or null.
*/
public void setMemberAttributeValueTemplate(String template) {
if (!StringUtils.hasText(template)) {
String msg = "User DN template cannot be null or empty.";
throw new IllegalArgumentException(msg);
}
int index = template.indexOf(MEMBER_SUBSTITUTION_TOKEN);
if (index < 0) {
String msg = "Member attribute value template must contain the '" + MEMBER_SUBSTITUTION_TOKEN
+ "' replacement token to understand how to " + "parse the group members.";
throw new IllegalArgumentException(msg);
}
String prefix = template.substring(0, index);
String suffix = template.substring(prefix.length() + MEMBER_SUBSTITUTION_TOKEN.length());
this.memberAttributeValuePrefix = prefix;
this.memberAttributeValueSuffix = suffix;
}
public void setRolesByGroup(Map<String, String> rolesByGroup) {
this.rolesByGroup.putAll(rolesByGroup);
}
public Map<String, String> getRolesByGroup() {
return rolesByGroup;
}
public void setPermissionsByRole(String permissionsByRoleStr) {
permissionsByRole.putAll(parsePermissionByRoleString(permissionsByRoleStr));
}
public Map<String, List<String>> getPermissionsByRole() {
return permissionsByRole;
}
public boolean isAuthorizationEnabled() {
return authorizationEnabled;
}
public void setAuthorizationEnabled(boolean authorizationEnabled) {
this.authorizationEnabled = authorizationEnabled;
}
public String getUserSearchAttributeName() {
return userSearchAttributeName;
}
/**
* Set User Search Attribute Name for LDAP.
*
* @param userSearchAttributeName
* userAttribute to search ldap.
*/
public void setUserSearchAttributeName(String userSearchAttributeName) {
if (userSearchAttributeName != null) {
userSearchAttributeName = userSearchAttributeName.trim();
}
this.userSearchAttributeName = userSearchAttributeName;
}
public String getUserObjectClass() {
return userObjectClass;
}
public void setUserObjectClass(String userObjectClass) {
this.userObjectClass = userObjectClass;
}
private Map<String, List<String>> parsePermissionByRoleString(String permissionsByRoleStr) {
Map<String, List<String>> perms = new HashMap<String, List<String>>();
// split by semicolon ; then by eq = then by comma ,
StringTokenizer stSem = new StringTokenizer(permissionsByRoleStr, ";");
while (stSem.hasMoreTokens()) {
String roleAndPerm = stSem.nextToken();
StringTokenizer stEq = new StringTokenizer(roleAndPerm, "=");
if (stEq.countTokens() != 2) {
continue;
}
String role = stEq.nextToken().trim();
String perm = stEq.nextToken().trim();
StringTokenizer stCom = new StringTokenizer(perm, ",");
List<String> permList = new ArrayList<String>();
while (stCom.hasMoreTokens()) {
permList.add(stCom.nextToken().trim());
}
perms.put(role, permList);
}
return perms;
}
boolean isUserMemberOfDynamicGroup(LdapName userLdapDn, String memberUrl,
final LdapContextFactory ldapContextFactory) throws NamingException {
// ldap://host:port/dn?attributes?scope?filter?extensions
if (memberUrl == null) {
return false;
}
String[] tokens = memberUrl.split("\\?");
if (tokens.length < 4) {
return false;
}
String searchBaseString = tokens[0].substring(tokens[0].lastIndexOf("/") + 1);
String searchScope = tokens[2];
String searchFilter = tokens[3];
LdapName searchBaseDn = new LdapName(searchBaseString);
// do scope test
if (searchScope.equalsIgnoreCase("base")) {
log.debug("DynamicGroup SearchScope base");
return false;
}
if (!userLdapDn.toString().endsWith(searchBaseDn.toString())) {
return false;
}
if (searchScope.equalsIgnoreCase("one") && (userLdapDn.size() != searchBaseDn.size() - 1)) {
log.debug("DynamicGroup SearchScope one");
return false;
}
// search for the filter, substituting base with userDn
// search for base_dn=userDn, scope=base, filter=filter
LdapContext systemLdapCtx = null;
systemLdapCtx = ldapContextFactory.getSystemLdapContext();
boolean member = false;
NamingEnumeration<SearchResult> searchResultEnum = null;
try {
searchResultEnum = systemLdapCtx.search(userLdapDn, searchFilter,
searchScope.equalsIgnoreCase("sub") ? SUBTREE_SCOPE : ONELEVEL_SCOPE);
if (searchResultEnum.hasMore()) {
return true;
}
} finally {
try {
if (searchResultEnum != null) {
searchResultEnum.close();
}
} finally {
LdapUtils.closeContext(systemLdapCtx);
}
}
return member;
}
public String getPrincipalRegex() {
return principalRegex;
}
/**
* Set Regex for Principal LDAP.
*
* @param regex
* regex to use to search for principal in shiro.
*/
public void setPrincipalRegex(String regex) {
if (regex == null || regex.trim().isEmpty()) {
principalPattern = Pattern.compile(DEFAULT_PRINCIPAL_REGEX);
principalRegex = DEFAULT_PRINCIPAL_REGEX;
} else {
regex = regex.trim();
Pattern pattern = Pattern.compile(regex);
principalPattern = pattern;
principalRegex = regex;
}
}
public String getUserSearchAttributeTemplate() {
return userSearchAttributeTemplate;
}
public void setUserSearchAttributeTemplate(final String template) {
this.userSearchAttributeTemplate = (template == null ? null : template.trim());
}
public String getUserSearchFilter() {
return userSearchFilter;
}
public void setUserSearchFilter(final String filter) {
this.userSearchFilter = (filter == null ? null : filter.trim());
}
public boolean getUserLowerCase() {
return userLowerCase;
}
public void setUserLowerCase(boolean userLowerCase) {
this.userLowerCase = userLowerCase;
}
public String getUserSearchScope() {
return userSearchScope;
}
public void setUserSearchScope(final String scope) {
this.userSearchScope = (scope == null ? null : scope.trim().toLowerCase());
}
public String getGroupSearchScope() {
return groupSearchScope;
}
public void setGroupSearchScope(final String scope) {
this.groupSearchScope = (scope == null ? null : scope.trim().toLowerCase());
}
private SearchControls getUserSearchControls() {
SearchControls searchControls = SUBTREE_SCOPE;
if ("onelevel".equalsIgnoreCase(userSearchScope)) {
searchControls = ONELEVEL_SCOPE;
} else if ("object".equalsIgnoreCase(userSearchScope)) {
searchControls = OBJECT_SCOPE;
}
return searchControls;
}
private SearchControls getGroupSearchControls() {
SearchControls searchControls = SUBTREE_SCOPE;
if ("onelevel".equalsIgnoreCase(groupSearchScope)) {
searchControls = ONELEVEL_SCOPE;
} else if ("object".equalsIgnoreCase(groupSearchScope)) {
searchControls = OBJECT_SCOPE;
}
return searchControls;
}
@Override
public void setUserDnTemplate(final String template) throws IllegalArgumentException {
userDnTemplate = template;
}
private Matcher matchPrincipal(final String principal) {
Matcher matchedPrincipal = principalPattern.matcher(principal);
if (!matchedPrincipal.matches()) {
throw new IllegalArgumentException("Principal "
+ principal + " does not match " + principalRegex);
}
return matchedPrincipal;
}
/**
* Returns the LDAP User Distinguished Name (DN) to use when acquiring an
* {@link javax.naming.ldap.LdapContext LdapContext} from the
* {@link LdapContextFactory}.
* <p/>
* If the the {@link #getUserDnTemplate() userDnTemplate} property has been
* set, this implementation will construct the User DN by substituting the
* specified {@code principal} into the configured template. If the
* {@link #getUserDnTemplate() userDnTemplate} has not been set, the method
* argument will be returned directly (indicating that the submitted
* authentication token principal <em>is</em> the User DN).
*
* @param principal
* the principal to substitute into the configured
* {@link #getUserDnTemplate() userDnTemplate}.
* @return the constructed User DN to use at runtime when acquiring an
* {@link javax.naming.ldap.LdapContext}.
* @throws IllegalArgumentException
* if the method argument is null or empty
* @throws IllegalStateException
* if the {@link #getUserDnTemplate userDnTemplate} has not been
* set.
* @see LdapContextFactory#getLdapContext(Object, Object)
*/
@Override
protected String getUserDn(final String principal) throws IllegalArgumentException,
IllegalStateException {
String userDn;
Matcher matchedPrincipal = matchPrincipal(principal);
String userSearchBase = getUserSearchBase();
String userSearchAttributeName = getUserSearchAttributeName();
// If not searching use the userDnTemplate and return.
if ((userSearchBase == null || userSearchBase.isEmpty()) || (userSearchAttributeName == null
&& userSearchFilter == null && !"object".equalsIgnoreCase(userSearchScope))) {
userDn = expandTemplate(userDnTemplate, matchedPrincipal);
if (log.isDebugEnabled()) {
log.debug("LDAP UserDN and Principal: " + userDn + "," + principal);
}
return userDn;
}
// Create the searchBase and searchFilter from config.
String searchBase = expandTemplate(getUserSearchBase(), matchedPrincipal);
String searchFilter = null;
if (userSearchFilter == null) {
if (userSearchAttributeName == null) {
searchFilter = String.format("(objectclass=%1$s)", getUserObjectClass());
} else {
searchFilter = String.format("(&(objectclass=%1$s)(%2$s=%3$s))", getUserObjectClass(),
userSearchAttributeName, expandTemplate(getUserSearchAttributeTemplate(),
matchedPrincipal));
}
} else {
searchFilter = expandTemplate(userSearchFilter, matchedPrincipal);
}
SearchControls searchControls = getUserSearchControls();
// Search for userDn and return.
LdapContext systemLdapCtx = null;
NamingEnumeration<SearchResult> searchResultEnum = null;
try {
systemLdapCtx = getContextFactory().getSystemLdapContext();
if (log.isDebugEnabled()) {
log.debug("SearchBase,SearchFilter,UserSearchScope: " + searchBase
+ "," + searchFilter + "," + userSearchScope);
}
searchResultEnum = systemLdapCtx.search(searchBase, searchFilter, searchControls);
// SearchResults contains all the entries in search scope
if (searchResultEnum.hasMore()) {
SearchResult searchResult = searchResultEnum.next();
userDn = searchResult.getNameInNamespace();
if (log.isDebugEnabled()) {
log.debug("UserDN Returned,Principal: " + userDn + "," + principal);
}
return userDn;
} else {
throw new IllegalArgumentException("Illegal principal name: " + principal);
}
} catch (AuthenticationException ne) {
ne.printStackTrace();
throw new IllegalArgumentException("Illegal principal name: " + principal);
} catch (NamingException ne) {
throw new IllegalArgumentException("Hit NamingException: " + ne.getMessage());
} finally {
try {
if (searchResultEnum != null) {
searchResultEnum.close();
}
} catch (NamingException ne) {
// Ignore exception on close.
} finally {
LdapUtils.closeContext(systemLdapCtx);
}
}
}
@Override
protected AuthenticationInfo createAuthenticationInfo(AuthenticationToken token,
Object ldapPrincipal,
Object ldapCredentials, LdapContext ldapContext) throws NamingException {
HashRequest.Builder builder = new HashRequest.Builder();
Hash credentialsHash = hashService
.computeHash(builder.setSource(token.getCredentials())
.setAlgorithmName(HASHING_ALGORITHM).build());
return new SimpleAuthenticationInfo(token.getPrincipal(),
credentialsHash.toHex(), credentialsHash.getSalt(),
getName());
}
private static final String expandTemplate(final String template, final Matcher input) {
String output = template;
Matcher matcher = TEMPLATE_PATTERN.matcher(output);
while (matcher.find()) {
String lookupStr = matcher.group(1);
int lookupIndex = Integer.parseInt(lookupStr);
String lookupValue = input.group(lookupIndex);
output = matcher.replaceFirst(lookupValue == null ? "" : lookupValue);
matcher = TEMPLATE_PATTERN.matcher(output);
}
return output;
}
}

View file

@ -24,7 +24,8 @@ import org.apache.shiro.realm.ldap.JndiLdapContextFactory;
import org.apache.shiro.realm.ldap.JndiLdapRealm;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.util.JdbcUtils;
import org.apache.zeppelin.server.ActiveDirectoryGroupRealm;
import org.apache.zeppelin.realm.ActiveDirectoryGroupRealm;
import org.apache.zeppelin.realm.LdapRealm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -114,8 +115,76 @@ public class GetUserList {
} catch (Exception e) {
LOG.error("Error retrieving User list from Ldap Realm", e);
}
LOG.info("UserList: " + userList);
return userList;
}
/**
* function to extract users from Zeppelin LdapRealm
*/
public List<String> getUserList(LdapRealm r, String searchText) {
List<String> userList = new ArrayList<>();
if (LOG.isDebugEnabled()) {
LOG.debug("SearchText: " + searchText);
}
String userAttribute = r.getUserSearchAttributeName();
String userSearchRealm = r.getUserSearchBase();
String userObjectClass = r.getUserObjectClass();
JndiLdapContextFactory CF = (JndiLdapContextFactory) r.getContextFactory();
try {
LdapContext ctx = CF.getSystemLdapContext();
SearchControls constraints = new SearchControls();
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
String[] attrIDs = {userAttribute};
constraints.setReturningAttributes(attrIDs);
NamingEnumeration result = ctx.search(userSearchRealm, "(&(objectclass=" +
userObjectClass + ")("
+ userAttribute + "=" + searchText + "))", constraints);
while (result.hasMore()) {
Attributes attrs = ((SearchResult) result.next()).getAttributes();
if (attrs.get(userAttribute) != null) {
String currentUser;
if (r.getUserLowerCase()) {
LOG.debug("userLowerCase true");
currentUser = ((String) attrs.get(userAttribute).get()).toLowerCase();
} else {
LOG.debug("userLowerCase false");
currentUser = (String) attrs.get(userAttribute).get();
}
if (LOG.isDebugEnabled()) {
LOG.debug("CurrentUser: " + currentUser);
}
userList.add(currentUser.trim());
}
}
} catch (Exception e) {
LOG.error("Error retrieving User list from Ldap Realm", e);
}
return userList;
}
/***
* Get user roles from shiro.ini for Zeppelin LdapRealm
* @param r
* @return
*/
public List<String> getRolesList(LdapRealm r) {
List<String> roleList = new ArrayList<>();
Map<String, String> roles = r.getListRoles();
if (roles != null) {
Iterator it = roles.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
if (LOG.isDebugEnabled()) {
LOG.debug("RoleKeyValue: " + pair.getKey() +
" = " + pair.getValue());
}
roleList.add((String) pair.getKey());
}
}
return roleList;
}
public List<String> getUserList(ActiveDirectoryGroupRealm r, String searchText) {
List<String> userList = new ArrayList<>();

View file

@ -80,6 +80,27 @@ public class InterpreterRestApi {
return new JsonResponse<>(Status.OK, "", interpreterFactory.get()).build();
}
/**
* Get a setting
*/
@GET
@Path("setting/{settingId}")
@ZeppelinApi
public Response getSetting(@PathParam("settingId") String settingId) {
try {
InterpreterSetting setting = interpreterFactory.get(settingId);
if (setting == null) {
return new JsonResponse<>(Status.NOT_FOUND).build();
} else {
return new JsonResponse<>(Status.OK, "", setting).build();
}
} catch (NullPointerException e) {
logger.error("Exception in InterpreterRestApi while creating ", e);
return new JsonResponse<>(Status.INTERNAL_SERVER_ERROR, e.getMessage(),
ExceptionUtils.getStackTrace(e)).build();
}
}
/**
* Add new interpreter setting
*
@ -209,7 +230,7 @@ public class InterpreterRestApi {
try {
Repository request = gson.fromJson(message, Repository.class);
interpreterFactory.addRepository(request.getId(), request.getUrl(), request.isSnapshot(),
request.getAuthentication(), request.getProxy());
request.getAuthentication(), request.getProxy());
logger.info("New repository {} added", request.getId());
} catch (Exception e) {
logger.error("Exception in InterpreterRestApi while adding repository ", e);
@ -225,7 +246,7 @@ public class InterpreterRestApi {
@GET
@Path("getmetainfos/{settingId}")
public Response getMetaInfo(@Context HttpServletRequest req,
@PathParam("settingId") String settingId) {
@PathParam("settingId") String settingId) {
String propName = req.getParameter("propName");
if (propName == null) {
return new JsonResponse<>(Status.BAD_REQUEST).build();

View file

@ -25,7 +25,8 @@ import org.apache.shiro.realm.ldap.JndiLdapRealm;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.zeppelin.annotation.ZeppelinApi;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.server.ActiveDirectoryGroupRealm;
import org.apache.zeppelin.realm.ActiveDirectoryGroupRealm;
import org.apache.zeppelin.realm.LdapRealm;
import org.apache.zeppelin.server.JsonResponse;
import org.apache.zeppelin.ticket.TicketContainer;
import org.apache.zeppelin.utils.SecurityUtils;
@ -105,16 +106,22 @@ public class SecurityRestApi {
if (realmsList != null) {
for (Iterator<Realm> iterator = realmsList.iterator(); iterator.hasNext(); ) {
Realm realm = iterator.next();
String name = realm.getName();
if (name.equals("iniRealm")) {
String name = realm.getClass().getName();
if (LOG.isDebugEnabled()) {
LOG.debug("RealmClass.getName: " + name);
}
if (name.equals("org.apache.shiro.realm.text.IniRealm")) {
usersList.addAll(getUserListObj.getUserList((IniRealm) realm));
rolesList.addAll(getUserListObj.getRolesList((IniRealm) realm));
} else if (name.equals("ldapRealm")) {
} else if (name.equals("org.apache.zeppelin.realm.LdapGroupRealm")) {
usersList.addAll(getUserListObj.getUserList((JndiLdapRealm) realm, searchText));
} else if (name.equals("activeDirectoryRealm")) {
} else if (name.equals("org.apache.zeppelin.realm.LdapRealm")) {
usersList.addAll(getUserListObj.getUserList((LdapRealm) realm, searchText));
rolesList.addAll(getUserListObj.getRolesList((LdapRealm) realm));
} else if (name.equals("org.apache.zeppelin.realm.ActiveDirectoryGroupRealm")) {
usersList.addAll(getUserListObj.getUserList((ActiveDirectoryGroupRealm) realm,
searchText));
} else if (name.equals("jdbcRealm")) {
} else if (name.equals("org.apache.shiro.realm.jdbc.JdbcRealm")) {
usersList.addAll(getUserListObj.getUserList((JdbcRealm) realm));
}
}

View file

@ -34,6 +34,10 @@ import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.realm.LdapRealm;
import org.mortbay.log.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
@ -45,6 +49,7 @@ public class SecurityUtils {
private static final String ANONYMOUS = "anonymous";
private static final HashSet<String> EMPTY_HASHSET = Sets.newHashSet();
private static boolean isEnabled = false;
private static final Logger log = LoggerFactory.getLogger(SecurityUtils.class);
public static void initSecurityManager(String shiroPath) {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("file:" + shiroPath);
@ -119,13 +124,15 @@ public class SecurityUtils {
Collection realmsList = SecurityUtils.getRealmsList();
for (Iterator<Realm> iterator = realmsList.iterator(); iterator.hasNext(); ) {
Realm realm = iterator.next();
String name = realm.getName();
if (name.equals("iniRealm")) {
String name = realm.getClass().getName();
if (name.equals("org.apache.shiro.realm.text.IniRealm")) {
allRoles = ((IniRealm) realm).getIni().get("roles");
break;
} else if (name.equals("org.apache.zeppelin.realm.LdapRealm")) {
allRoles = ((LdapRealm) realm).getListRoles();
break;
}
}
if (allRoles != null) {
Iterator it = allRoles.entrySet().iterator();
while (it.hasNext()) {

View file

@ -35,7 +35,6 @@ import org.slf4j.LoggerFactory;
public class SparkParagraphIT extends AbstractZeppelinIT {
private static final Logger LOG = LoggerFactory.getLogger(SparkParagraphIT.class);
@Rule
public ErrorCollector collector = new ErrorCollector();
@ -142,8 +141,8 @@ public class SparkParagraphIT extends AbstractZeppelinIT {
// the last statement's evaluation result is printed
setTextOfParagraph(2, "%pyspark\\n" +
"sc.version\\n" +
"1+1");
"sc.version\\n" +
"1+1");
runParagraph(2);
try {
waitForParagraph(2, "FINISHED");
@ -154,9 +153,9 @@ public class SparkParagraphIT extends AbstractZeppelinIT {
);
}
WebElement paragraph2Result = driver.findElement(By.xpath(
getParagraphXPath(2) + "//div[@class=\"tableDisplay\"]"));
getParagraphXPath(2) + "//div[@class=\"tableDisplay\"]"));
collector.checkThat("Paragraph from SparkParagraphIT of testPySpark result: ",
paragraph2Result.getText().toString(), CoreMatchers.equalTo("2")
paragraph2Result.getText().toString(), CoreMatchers.equalTo("2")
);
} catch (Exception e) {
@ -192,4 +191,53 @@ public class SparkParagraphIT extends AbstractZeppelinIT {
handleException("Exception in SparkParagraphIT while testSqlSpark", e);
}
}
@Test
public void testDep() throws Exception {
if (!endToEndTestEnabled()) {
return;
}
try {
// restart spark interpreter before running %dep
clickAndWait(By.xpath("//span[@tooltip='Interpreter binding']"));
clickAndWait(By.xpath("//div[font[contains(text(), 'spark')]]/preceding-sibling::a[@tooltip='Restart']"));
clickAndWait(By.xpath("//button[contains(.,'OK')]"));
setTextOfParagraph(1,"%dep z.load(\"org.apache.commons:commons-csv:1.1\")");
runParagraph(1);
try {
waitForParagraph(1, "FINISHED");
WebElement paragraph1Result = driver.findElement(By.xpath(getParagraphXPath(1) +
"//div[@class='text']"));
collector.checkThat("Paragraph from SparkParagraphIT of testSqlSpark result: ",
paragraph1Result.getText(), CoreMatchers.containsString("res0: org.apache.zeppelin.dep.Dependency = org.apache.zeppelin.dep.Dependency"));
setTextOfParagraph(2, "import org.apache.commons.csv.CSVFormat");
runParagraph(2);
try {
waitForParagraph(2, "FINISHED");
WebElement paragraph2Result = driver.findElement(By.xpath(getParagraphXPath(2) +
"//div[@class='text']"));
collector.checkThat("Paragraph from SparkParagraphIT of testSqlSpark result: ",
paragraph2Result.getText(), CoreMatchers.equalTo("import org.apache.commons.csv.CSVFormat"));
} catch (TimeoutException e) {
waitForParagraph(2, "ERROR");
collector.checkThat("Second paragraph from SparkParagraphIT of testDep status: ",
"ERROR", CoreMatchers.equalTo("FINISHED")
);
}
} catch (TimeoutException e) {
waitForParagraph(1, "ERROR");
collector.checkThat("First paragraph from SparkParagraphIT of testDep status: ",
"ERROR", CoreMatchers.equalTo("FINISHED")
);
}
} catch (Exception e) {
handleException("Exception in SparkParagraphIT while testDep", e);
}
}
}

View file

@ -18,9 +18,11 @@
package org.apache.zeppelin.rest;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
@ -40,7 +42,6 @@ import org.junit.Test;
import org.junit.runners.MethodSorters;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import static org.junit.Assert.*;
@ -71,13 +72,12 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
public void getAvailableInterpreters() throws IOException {
// when
GetMethod get = httpGet("/interpreter");
JsonObject body = getBodyFieldFromResponse(get.getResponseBodyAsString());
// then
assertThat(get, isAllowed());
Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {
}.getType());
Map<String, Object> body = (Map<String, Object>) resp.get("body");
assertEquals(ZeppelinServer.notebook.getInterpreterFactory().getAvailableInterpreterSettings().size(), body.size());
assertEquals(ZeppelinServer.notebook.getInterpreterFactory().getAvailableInterpreterSettings().size(),
body.entrySet().size());
get.releaseConnection();
}
@ -85,45 +85,63 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
public void getSettings() throws IOException {
// when
GetMethod get = httpGet("/interpreter/setting");
// then
assertThat(get, isAllowed());
// DO NOT REMOVE: implies that body is properly parsed as an array
JsonArray body = getArrayBodyFieldFromResponse(get.getResponseBodyAsString());
get.releaseConnection();
}
@Test
public void testGetNonExistInterpreterSetting() throws IOException {
// when
String nonExistInterpreterSettingId = "apache_.zeppelin_1s_.aw3some$";
GetMethod get = httpGet("/interpreter/setting/" + nonExistInterpreterSettingId);
// then
Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {
}.getType());
assertThat(get, isAllowed());
assertThat("Test get method:", get, isNotFound());
get.releaseConnection();
}
@Test
public void testSettingsCRUD() throws IOException {
// Call Create Setting REST API
String jsonRequest = "{\"name\":\"md2\",\"group\":\"md\",\"properties\":{\"propname\":\"propvalue\"}," +
// when: call create setting API
String rawRequest = "{\"name\":\"md2\",\"group\":\"md\",\"properties\":{\"propname\":\"propvalue\"}," +
"\"interpreterGroup\":[{\"class\":\"org.apache.zeppelin.markdown.Markdown\",\"name\":\"md\"}]," +
"\"dependencies\":[]," +
"\"option\": { \"remote\": true, \"session\": false }}";
PostMethod post = httpPost("/interpreter/setting/", jsonRequest);
JsonObject jsonRequest = gson.fromJson(rawRequest, JsonElement.class).getAsJsonObject();
PostMethod post = httpPost("/interpreter/setting/", jsonRequest.toString());
String postResponse = post.getResponseBodyAsString();
LOG.info("testSettingCRUD create response\n" + post.getResponseBodyAsString());
InterpreterSetting created = convertResponseToInterpreterSetting(postResponse);
String newSettingId = created.getId();
// then : call create setting API
assertThat("test create method:", post, isCreated());
Map<String, Object> resp = gson.fromJson(post.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {
}.getType());
Map<String, Object> body = (Map<String, Object>) resp.get("body");
//extract id from body string {id=2AWMQDNX7, name=md2, group=md,
String newSettingId = body.toString().split(",")[0].split("=")[1];
post.releaseConnection();
// Call Update Setting REST API
jsonRequest = "{\"name\":\"md2\",\"group\":\"md\",\"properties\":{\"propname\":\"Otherpropvalue\"}," +
"\"interpreterGroup\":[{\"class\":\"org.apache.zeppelin.markdown.Markdown\",\"name\":\"md\"}]," +
"\"dependencies\":[]," +
"\"option\": { \"remote\": true, \"session\": false }}";
PutMethod put = httpPut("/interpreter/setting/" + newSettingId, jsonRequest);
// when: call read setting API
GetMethod get = httpGet("/interpreter/setting/" + newSettingId);
String getResponse = get.getResponseBodyAsString();
LOG.info("testSettingCRUD get response\n" + getResponse);
InterpreterSetting previouslyCreated = convertResponseToInterpreterSetting(getResponse);
// then : read Setting API
assertThat("Test get method:", get, isAllowed());
assertEquals(newSettingId, previouslyCreated.getId());
get.releaseConnection();
// when: call update setting API
jsonRequest.getAsJsonObject("properties").addProperty("propname2", "this is new prop");
PutMethod put = httpPut("/interpreter/setting/" + newSettingId, jsonRequest.toString());
LOG.info("testSettingCRUD update response\n" + put.getResponseBodyAsString());
// then: call update setting API
assertThat("test update method:", put, isAllowed());
put.releaseConnection();
// Call Delete Setting REST API
// when: call delete setting API
DeleteMethod delete = httpDelete("/interpreter/setting/" + newSettingId);
LOG.info("testSettingCRUD delete response\n" + delete.getResponseBodyAsString());
// then: call delete setting API
assertThat("Test delete method:", delete, isAllowed());
delete.releaseConnection();
}
@ -139,33 +157,29 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
@Test
public void testInterpreterAutoBinding() throws IOException {
// create note
// when
Note note = ZeppelinServer.notebook.createNote(anonymous);
// check interpreter is binded
GetMethod get = httpGet("/notebook/interpreter/bind/" + note.getId());
assertThat(get, isAllowed());
get.addRequestHeader("Origin", "http://localhost");
Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {
}.getType());
List<Map<String, String>> body = (List<Map<String, String>>) resp.get("body");
assertTrue(0 < body.size());
JsonArray body = getArrayBodyFieldFromResponse(get.getResponseBodyAsString());
// then: check interpreter is binded
assertTrue(0 < body.size());
get.releaseConnection();
//cleanup
ZeppelinServer.notebook.removeNote(note.getId(), anonymous);
}
@Test
public void testInterpreterRestart() throws IOException, InterruptedException {
// create new note
// when: create new note
Note note = ZeppelinServer.notebook.createNote(anonymous);
note.addParagraph();
Paragraph p = note.getLastParagraph();
Map config = p.getConfig();
config.put("enabled", true);
// run markdown paragraph
// when: run markdown paragraph
p.setConfig(config);
p.setText("%md markdown");
p.setAuthenticationInfo(anonymous);
@ -175,10 +189,10 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
}
assertEquals(p.getResult().message(), getSimulatedMarkdownResult("markdown"));
// restart interpreter
// when: restart interpreter
for (InterpreterSetting setting : ZeppelinServer.notebook.getInterpreterFactory().getInterpreterSettings(note.getId())) {
if (setting.getName().equals("md")) {
// Call Restart Interpreter REST API
// call restart interpreter API
PutMethod put = httpPut("/interpreter/setting/restart/" + setting.getId(), "");
assertThat("test interpreter restart:", put, isAllowed());
put.releaseConnection();
@ -186,7 +200,7 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
}
}
// run markdown paragraph, again
// when: run markdown paragraph, again
p = note.addParagraph();
p.setConfig(config);
p.setText("%md markdown restarted");
@ -195,21 +209,22 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
while (p.getStatus() != Status.FINISHED) {
Thread.sleep(100);
}
// then
assertEquals(p.getResult().message(), getSimulatedMarkdownResult("markdown restarted"));
//cleanup
ZeppelinServer.notebook.removeNote(note.getId(), anonymous);
}
@Test
public void testRestartInterpreterPerNote() throws IOException, InterruptedException {
// create new note
// when: create new note
Note note = ZeppelinServer.notebook.createNote(anonymous);
note.addParagraph();
Paragraph p = note.getLastParagraph();
Map config = p.getConfig();
config.put("enabled", true);
// run markdown paragraph.
// when: run markdown paragraph.
p.setConfig(config);
p.setText("%md markdown");
p.setAuthenticationInfo(anonymous);
@ -219,7 +234,7 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
}
assertEquals(p.getResult().message(), getSimulatedMarkdownResult("markdown"));
// get md interpreter
// when: get md interpreter
InterpreterSetting mdIntpSetting = null;
for (InterpreterSetting setting : ZeppelinServer.notebook.getInterpreterFactory().getInterpreterSettings(note.getId())) {
if (setting.getName().equals("md")) {
@ -260,7 +275,7 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
@Test
public void testAddDeleteRepository() throws IOException {
// Call create repository REST API
// Call create repository API
String repoId = "securecentral";
String jsonRequest = "{\"id\":\"" + repoId +
"\",\"url\":\"https://repo1.maven.org/maven2\",\"snapshot\":\"false\"}";
@ -269,12 +284,26 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
assertThat("Test create method:", post, isCreated());
post.releaseConnection();
// Call delete repository REST API
// Call delete repository API
DeleteMethod delete = httpDelete("/interpreter/repository/" + repoId);
assertThat("Test delete method:", delete, isAllowed());
delete.releaseConnection();
}
public JsonObject getBodyFieldFromResponse(String rawResponse) {
JsonObject response = gson.fromJson(rawResponse, JsonElement.class).getAsJsonObject();
return response.getAsJsonObject("body");
}
public JsonArray getArrayBodyFieldFromResponse(String rawResponse) {
JsonObject response = gson.fromJson(rawResponse, JsonElement.class).getAsJsonObject();
return response.getAsJsonArray("body");
}
public InterpreterSetting convertResponseToInterpreterSetting(String rawResponse) {
return gson.fromJson(getBodyFieldFromResponse(rawResponse), InterpreterSetting.class);
}
public static String getSimulatedMarkdownResult(String markdown) {
return String.format("<div class=\"markdown-body\">\n<p>%s</p>\n</div>", markdown);
}

View file

@ -171,7 +171,8 @@ a.navbar-brand:hover {
color: #000;
height: 28px;
width: 200px;
font: normal normal normal 14px/1 FontAwesome;
font-size: 14px;
font-family: 'Helvetica Neue', Helvetica, Arial, 'FontAwesome', sans-serif;
}
.dropdown-submenu {

View file

@ -90,7 +90,7 @@ limitations under the License.
</div>
<div class="box width-full"
ng-repeat="setting in interpreterSettings | orderBy: 'name' | filter: searchInterpreter" interpreter-directive>
ng-repeat="setting in interpreterSettings | orderBy: 'name' | filter: {name:searchInterpreter} " interpreter-directive>
<div id="{{setting.name | lowercase}}">
<div class="row interpreter">

View file

@ -33,7 +33,7 @@ limitations under the License.
as-sortable="interpreterSelectionListeners" data-ng-model="interpreterBindings">
<div data-ng-repeat="item in interpreterBindings" as-sortable-item>
<div>
<a ng-click="restartInterpreter(item)"
<a ng-click="restartInterpreter(item)"
ng-class="{'inactivelink': !item.selected}"
tooltip="Restart">
<span class="glyphicon glyphicon-refresh btn-md"></span>
@ -83,18 +83,18 @@ limitations under the License.
</select>
Owners can change permissions,read and write the note.
</p>
<p><span class="readers">Readers </span>
<select id="selectReaders" multiple="multiple">
<option ng-repeat="readers in permissions.readers" selected="selected">{{readers}}</option>
</select>
Readers can only read the note.
</p>
<p><span class="writers">Writers </span>
<select id="selectWriters" multiple="multiple">
<option ng-repeat="writers in permissions.writers" selected="selected">{{writers}}</option>
</select>
Writers can read and write the note.
</p>
<p><span class="readers">Readers </span>
<select id="selectReaders" multiple="multiple">
<option ng-repeat="readers in permissions.readers" selected="selected">{{readers}}</option>
</select>
Readers can only read the note.
</p>
</div>
</div>
<br />

View file

@ -11,4 +11,5 @@ 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.
-->
<input type="text" class="note-name-query form-control" ng-click="$event.stopPropagation()" placeholder="&#xf002 Filter" ng-model="$parent.query.q" />
<input type="text" class="note-name-query form-control" ng-click="$event.stopPropagation()"
placeholder="&#xf002 Filter" ng-model="$parent.query.q" />

View file

@ -435,6 +435,10 @@ public class ZeppelinConfiguration extends XMLConfiguration {
return getBoolean(ConfVars.ZEPPELIN_ANONYMOUS_ALLOWED);
}
public boolean isNotebokPublic() {
return getBoolean(ConfVars.ZEPPELIN_NOTEBOOK_PUBLIC);
}
public String getConfDir() {
return getString(ConfVars.ZEPPELIN_CONF_DIR);
}
@ -570,6 +574,8 @@ public class ZeppelinConfiguration extends XMLConfiguration {
ZEPPELIN_NOTEBOOK_AZURE_USER("zeppelin.notebook.azure.user", "user"),
ZEPPELIN_NOTEBOOK_STORAGE("zeppelin.notebook.storage", VFSNotebookRepo.class.getName()),
ZEPPELIN_NOTEBOOK_ONE_WAY_SYNC("zeppelin.notebook.one.way.sync", false),
// whether by default note is public or private
ZEPPELIN_NOTEBOOK_PUBLIC("zeppelin.notebook.public", true),
ZEPPELIN_INTERPRETER_REMOTE_RUNNER("zeppelin.interpreter.remoterunner",
System.getProperty("os.name")
.startsWith("Windows") ? "bin/interpreter.cmd" : "bin/interpreter.sh"),

View file

@ -166,11 +166,7 @@ public class Notebook implements NoteEventListener {
bindInterpretersToNote(subject.getUser(), note.getId(), interpreterIds);
}
if (subject != null && !"anonymous".equals(subject.getUser())) {
Set<String> owners = new HashSet<>();
owners.add(subject.getUser());
notebookAuthorization.setOwners(note.getId(), owners);
}
notebookAuthorization.setNewNotePermissions(note.getId(), subject);
noteSearchService.addIndexDoc(note);
note.persist(subject);
fireNoteCreateEvent(note);
@ -225,6 +221,7 @@ public class Notebook implements NoteEventListener {
newNote.addCloneParagraph(p);
}
notebookAuthorization.setNewNotePermissions(newNote.getId(), subject);
newNote.persist(subject);
} catch (IOException e) {
logger.error(e.toString(), e);

View file

@ -156,6 +156,10 @@ public class NotebookAuthorization {
LOG.error("Error saving notebook authorization file: " + e.getMessage());
}
}
public boolean isPublic() {
return conf.isNotebokPublic();
}
private Set<String> validateUser(Set<String> users) {
Set<String> returnUser = new HashSet<>();
@ -325,4 +329,26 @@ public class NotebookAuthorization {
}
}).toList();
}
public void setNewNotePermissions(String noteId, AuthenticationInfo subject) {
if (!AuthenticationInfo.isAnonymous(subject)) {
if (isPublic()) {
// add current user to owners - can be public
Set<String> owners = getOwners(noteId);
owners.add(subject.getUser());
setOwners(noteId, owners);
} else {
// add current user to owners, readers, writers - private note
Set<String> entities = getOwners(noteId);
entities.add(subject.getUser());
setOwners(noteId, entities);
entities = getReaders(noteId);
entities.add(subject.getUser());
setReaders(noteId, entities);
entities = getWriters(noteId);
entities.add(subject.getUser());
setWriters(noteId, entities);
}
}
}
}

View file

@ -81,12 +81,8 @@ public class VFSNotebookRepo implements NotebookRepo {
}
if (filesystemRoot.getScheme() == null) { // it is local path
try {
this.filesystemRoot = new URI(new File(
conf.getRelativeDir(filesystemRoot.getPath())).getAbsolutePath());
} catch (URISyntaxException e) {
throw new IOException(e);
}
File f = new File(conf.getRelativeDir(filesystemRoot.getPath()));
this.filesystemRoot = f.toURI();
}
fsManager = VFS.getManager();

View file

@ -24,6 +24,8 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
import java.net.MalformedURLException;
import java.util.List;
@ -87,4 +89,12 @@ public class ZeppelinConfigurationTest {
String notebookLocation = conf.getNotebookDir();
Assert.assertEquals("notebook", notebookLocation);
}
@Test
public void isNotebookPublicTest() throws ConfigurationException {
ZeppelinConfiguration conf = new ZeppelinConfiguration(this.getClass().getResource("/zeppelin-site.xml"));
boolean isIt = conf.isNotebokPublic();
assertTrue(isIt);
}
}

View file

@ -42,6 +42,7 @@ import org.apache.zeppelin.interpreter.remote.RemoteInterpreter;
import org.apache.zeppelin.notebook.JobListenerFactory;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.Notebook;
import org.apache.zeppelin.notebook.NotebookAuthorization;
import org.apache.zeppelin.notebook.repo.NotebookRepo;
import org.apache.zeppelin.notebook.repo.VFSNotebookRepo;
import org.apache.zeppelin.scheduler.SchedulerFactory;
@ -66,6 +67,7 @@ public class InterpreterFactoryTest {
private NotebookRepo notebookRepo;
private DependencyResolver depResolver;
private SchedulerFactory schedulerFactory;
private NotebookAuthorization notebookAuthorization;
@Mock
private JobListenerFactory jobListenerFactory;
@ -97,8 +99,9 @@ public class InterpreterFactoryTest {
SearchService search = mock(SearchService.class);
notebookRepo = new VFSNotebookRepo(conf);
notebookAuthorization = NotebookAuthorization.init(conf);
notebook = new Notebook(conf, notebookRepo, schedulerFactory, factory, jobListenerFactory, search,
null, null);
notebookAuthorization, null);
}
@After

View file

@ -1062,6 +1062,69 @@ public class NotebookTest implements JobListenerFactory{
assertEquals(notes2.size(), 1);
}
@Test
public void testPublicPrivateNewNote() throws IOException, SchedulerException {
HashSet<String> user1 = Sets.newHashSet("user1");
HashSet<String> user2 = Sets.newHashSet("user2");
// case of public note
assertTrue(conf.isNotebokPublic());
assertTrue(notebookAuthorization.isPublic());
List<Note> notes1 = notebook.getAllNotes(user1);
List<Note> notes2 = notebook.getAllNotes(user2);
assertEquals(notes1.size(), 0);
assertEquals(notes2.size(), 0);
// user1 creates note
Note notePublic = notebook.createNote(new AuthenticationInfo("user1"));
// both users have note
notes1 = notebook.getAllNotes(user1);
notes2 = notebook.getAllNotes(user2);
assertEquals(notes1.size(), 1);
assertEquals(notes2.size(), 1);
assertEquals(notes1.get(0).getId(), notePublic.getId());
assertEquals(notes2.get(0).getId(), notePublic.getId());
// user1 is only owner
assertEquals(notebookAuthorization.getOwners(notePublic.getId()).size(), 1);
assertEquals(notebookAuthorization.getReaders(notePublic.getId()).size(), 0);
assertEquals(notebookAuthorization.getWriters(notePublic.getId()).size(), 0);
// case of private note
System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_PUBLIC.getVarName(), "false");
ZeppelinConfiguration conf2 = ZeppelinConfiguration.create();
assertFalse(conf2.isNotebokPublic());
// notebook authorization reads from conf, so no need to re-initilize
assertFalse(notebookAuthorization.isPublic());
// check that still 1 note per user
notes1 = notebook.getAllNotes(user1);
notes2 = notebook.getAllNotes(user2);
assertEquals(notes1.size(), 1);
assertEquals(notes2.size(), 1);
// create private note
Note notePrivate = notebook.createNote(new AuthenticationInfo("user1"));
// only user1 have notePrivate right after creation
notes1 = notebook.getAllNotes(user1);
notes2 = notebook.getAllNotes(user2);
assertEquals(notes1.size(), 2);
assertEquals(notes2.size(), 1);
assertEquals(notes1.get(1).getId(), notePrivate.getId());
// user1 have all rights
assertEquals(notebookAuthorization.getOwners(notePrivate.getId()).size(), 1);
assertEquals(notebookAuthorization.getReaders(notePrivate.getId()).size(), 1);
assertEquals(notebookAuthorization.getWriters(notePrivate.getId()).size(), 1);
//set back public to true
System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_PUBLIC.getVarName(), "true");
ZeppelinConfiguration.create();
}
private void delete(File file){
if(file.isFile()) file.delete();
else if(file.isDirectory()){

View file

@ -35,6 +35,7 @@ import org.apache.zeppelin.interpreter.mock.MockInterpreter1;
import org.apache.zeppelin.notebook.JobListenerFactory;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.Notebook;
import org.apache.zeppelin.notebook.NotebookAuthorization;
import org.apache.zeppelin.notebook.Paragraph;
import org.apache.zeppelin.notebook.ParagraphJobListener;
import org.apache.zeppelin.scheduler.SchedulerFactory;
@ -56,6 +57,7 @@ public class VFSNotebookRepoTest implements JobListenerFactory {
private NotebookRepo notebookRepo;
private InterpreterFactory factory;
private DependencyResolver depResolver;
private NotebookAuthorization notebookAuthorization;
private File mainZepDir;
private File mainNotebookDir;
@ -86,7 +88,9 @@ public class VFSNotebookRepoTest implements JobListenerFactory {
SearchService search = mock(SearchService.class);
notebookRepo = new VFSNotebookRepo(conf);
notebook = new Notebook(conf, notebookRepo, schedulerFactory, factory, this, search, null, null);
notebookAuthorization = NotebookAuthorization.init(conf);
notebook = new Notebook(conf, notebookRepo, schedulerFactory, factory, this, search,
notebookAuthorization, null);
}
@After

View file

@ -148,5 +148,11 @@
</property>
-->
<property>
<name>zeppelin.notebook.public</name>
<value>true</value>
<description>Make notebook public by default when created, private otherwise</description>
</property>
</configuration>