Merge branch 'master' into jdbc-impersonation

This commit is contained in:
astroshim 2016-11-22 15:11:02 +09:00
commit 63f5ea7722
72 changed files with 2100 additions and 268 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

@ -23,7 +23,7 @@ function usage() {
echo "usage) $0 -p <port> -d <interpreter dir to load> -l <local interpreter repo dir to load>"
}
while getopts "hp:d:l:v" o; do
while getopts "hp:d:l:v:u:" o; do
case ${o} in
h)
usage
@ -42,6 +42,14 @@ while getopts "hp:d:l:v" o; do
. "${bin}/common.sh"
getZeppelinVersion
;;
u)
ZEPPELIN_IMPERSONATE_USER="${OPTARG}"
if [[ -z "$ZEPPELIN_IMPERSONATE_CMD" ]]; then
ZEPPELIN_IMPERSONATE_RUN_CMD=`echo "ssh ${ZEPPELIN_IMPERSONATE_USER}@localhost" `
else
ZEPPELIN_IMPERSONATE_RUN_CMD=$(eval "echo ${ZEPPELIN_IMPERSONATE_CMD} ")
fi
;;
esac
done
@ -178,9 +186,9 @@ addJarInDirForIntp "${LOCAL_INTERPRETER_REPO}"
CLASSPATH+=":${ZEPPELIN_INTP_CLASSPATH}"
if [[ -n "${SPARK_SUBMIT}" ]]; then
${SPARK_SUBMIT} --class ${ZEPPELIN_SERVER} --driver-class-path "${ZEPPELIN_INTP_CLASSPATH_OVERRIDES}:${CLASSPATH}" --driver-java-options "${JAVA_INTP_OPTS}" ${SPARK_SUBMIT_OPTIONS} ${SPARK_APP_JAR} ${PORT} &
${ZEPPELIN_IMPERSONATE_RUN_CMD} `${SPARK_SUBMIT} --class ${ZEPPELIN_SERVER} --driver-class-path "${ZEPPELIN_INTP_CLASSPATH_OVERRIDES}:${CLASSPATH}" --driver-java-options "${JAVA_INTP_OPTS}" ${SPARK_SUBMIT_OPTIONS} ${SPARK_APP_JAR} ${PORT} &`
else
${ZEPPELIN_RUNNER} ${JAVA_INTP_OPTS} ${ZEPPELIN_INTP_MEM} -cp ${ZEPPELIN_INTP_CLASSPATH_OVERRIDES}:${CLASSPATH} ${ZEPPELIN_SERVER} ${PORT} &
${ZEPPELIN_IMPERSONATE_RUN_CMD} ${ZEPPELIN_RUNNER} ${JAVA_INTP_OPTS} ${ZEPPELIN_INTP_MEM} -cp ${ZEPPELIN_INTP_CLASSPATH_OVERRIDES}:${CLASSPATH} ${ZEPPELIN_SERVER} ${PORT} &
fi
pid=$!

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 ####
@ -79,3 +80,4 @@
# export ZEPPELINHUB_API_ADDRESS # Refers to the address of the ZeppelinHub service in use
# export ZEPPELINHUB_API_TOKEN # Refers to the Zeppelin instance token of the user
# export ZEPPELINHUB_USER_KEY # Optional, when using Zeppelin with authentication.
# export ZEPPELIN_IMPERSONATE_CMD # Optional, when user want to run interpreter as end web user. eg) 'sudo -u ${ZEPPELIN_IMPERSONATE_USER}'

View file

@ -190,7 +190,7 @@
<property>
<name>zeppelin.interpreters</name>
<value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.rinterpreter.RRepl,org.apache.zeppelin.rinterpreter.KnitR,org.apache.zeppelin.spark.SparkRInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.file.HDFSFileInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,,org.apache.zeppelin.python.PythonInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter,org.apache.zeppelin.geode.GeodeOqlInterpreter,org.apache.zeppelin.postgresql.PostgreSqlInterpreter,org.apache.zeppelin.jdbc.JDBCInterpreter,org.apache.zeppelin.kylin.KylinInterpreter,org.apache.zeppelin.elasticsearch.ElasticsearchInterpreter,org.apache.zeppelin.scalding.ScaldingInterpreter,org.apache.zeppelin.alluxio.AlluxioInterpreter,org.apache.zeppelin.hbase.HbaseInterpreter,org.apache.zeppelin.livy.LivySparkInterpreter,org.apache.zeppelin.livy.LivyPySparkInterpreter,org.apache.zeppelin.livy.LivySparkRInterpreter,org.apache.zeppelin.livy.LivySparkSQLInterpreter,org.apache.zeppelin.bigquery.BigQueryInterpreter,org.apache.zeppelin.beam.BeamInterpreter,org.apache.zeppelin.pig.PigInterpreter,org.apache.zeppelin.pig.PigQueryInterpreter,org.apache.zeppelin.scio.ScioInterpreter</value>
<value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.rinterpreter.RRepl,org.apache.zeppelin.rinterpreter.KnitR,org.apache.zeppelin.spark.SparkRInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.file.HDFSFileInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,,org.apache.zeppelin.python.PythonInterpreter,org.apache.zeppelin.python.PythonInterpreterPandasSql,org.apache.zeppelin.python.PythonCondaInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter,org.apache.zeppelin.geode.GeodeOqlInterpreter,org.apache.zeppelin.postgresql.PostgreSqlInterpreter,org.apache.zeppelin.jdbc.JDBCInterpreter,org.apache.zeppelin.kylin.KylinInterpreter,org.apache.zeppelin.elasticsearch.ElasticsearchInterpreter,org.apache.zeppelin.scalding.ScaldingInterpreter,org.apache.zeppelin.alluxio.AlluxioInterpreter,org.apache.zeppelin.hbase.HbaseInterpreter,org.apache.zeppelin.livy.LivySparkInterpreter,org.apache.zeppelin.livy.LivyPySparkInterpreter,org.apache.zeppelin.livy.LivySparkRInterpreter,org.apache.zeppelin.livy.LivySparkSQLInterpreter,org.apache.zeppelin.bigquery.BigQueryInterpreter,org.apache.zeppelin.beam.BeamInterpreter,org.apache.zeppelin.pig.PigInterpreter,org.apache.zeppelin.pig.PigQueryInterpreter,org.apache.zeppelin.scio.ScioInterpreter</value>
<description>Comma separated interpreter configurations. First interpreter become a default</description>
</property>
@ -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

@ -46,6 +46,7 @@
<li><a href="{{BASE_PATH}}/manual/interpreterinstallation.html">Interpreter Installation</a></li>
<!--<li><a href="{{BASE_PATH}}/manual/dynamicinterpreterload.html">Dynamic Interpreter Loading</a></li>-->
<li><a href="{{BASE_PATH}}/manual/dependencymanagement.html">Interpreter Dependency Management</a></li>
<li><a href="{{BASE_PATH}}/manual/userimpersonation.html">Interpreter User Impersonation</a></li>
<li role="separator" class="divider"></li>
<li class="title"><span><b>Available Interpreters</b><span></li>
<li><a href="{{BASE_PATH}}/interpreter/alluxio.html">Alluxio</a></li>

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 KiB

View file

@ -142,6 +142,7 @@ Join to our [Mailing list](https://zeppelin.apache.org/community.html) and repor
* Usage
* [Interpreter Installation](./manual/interpreterinstallation.html): Install not only community managed interpreters but also 3rd party interpreters
* [Interpreter Dependency Management](./manual/dependencymanagement.html) when you include external libraries to interpreter
* [Interpreter User Impersonation](./manual/userimpersonation.html) when you want to run interpreter as end user
* Available Interpreters: currently, about 20 interpreters are available in Apache Zeppelin.
####Display System

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).
@ -95,7 +95,7 @@ Congratulations, you have successfully installed Apache Zeppelin! Here are few s
* Check [JDBC Interpreter](../interpreter/jdbc.html) to know more about configure and uses multiple JDBC data sources.
#### Zeppelin with Python ...
* Check [Python interpreter](../interpreter/python.html) to know more about Matplotlib, Pandas integration.
* Check [Python interpreter](../interpreter/python.html) to know more about Matplotlib, Pandas, Conda integration.
#### Multi-user environment ...
@ -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

@ -59,6 +59,31 @@ To access the help, type **help()**
## Python modules
The interpreter can use all modules already installed (with pip, easy_install...)
## Conda
[Conda](http://conda.pydata.org/) is an package management system and environment management system for python.
`%python.conda` interpreter lets you change between environments.
#### Usage
List your environments
```
%python.conda
```
Activate an environment
```
%python.conda activate [ENVIRONMENT_NAME]
```
Deactivate
```
%python.conda deactivate
```
## Using Zeppelin Dynamic Forms
You can leverage [Zeppelin Dynamic Form]({{BASE_PATH}}/manual/dynamicform.html) inside your Python code.

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

@ -0,0 +1,66 @@
---
layout: page
title: "Run zeppelin interpreter process as web front end user"
description: "Set up zeppelin interpreter process as web front end user."
group: manual
---
<!--
Licensed 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.
-->
{% include JB/setup %}
## Run zeppelin interpreter process as web front end user
* Enable shiro auth in shiro.ini
```
[users]
user1 = password1, role1
user2 = password2, role2
```
* Enable password-less ssh for the user you want to impersonate (say user1).
```
adduser user1
#ssh-keygen (optional if you don't already have generated ssh-key.
ssh user1@localhost mkdir -p .ssh
cat ~/.ssh/id_rsa.pub | ssh user1@localhost 'cat >> .ssh/authorized_keys'
```
* Start zeppelin server.
<hr>
<div class="row">
<div class="col-md-12">
<b> Screenshot </b>
<br /><br />
</div>
<div class="col-md-12" >
<a data-lightbox="compiler" href="../assets/themes/zeppelin/img/screenshots/user-impersonation.gif">
<img class="img-responsive" src="../assets/themes/zeppelin/img/screenshots/user-impersonation.gif" />
</a>
</div>
</div>
<hr>
* Go to interpreter setting page, and enable "User Impersonate" in any of the interpreter (in my example its shell interpreter)
* Test with a simple paragraph
```
%sh
whoami
```

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

@ -334,7 +334,7 @@
"progressUpdateIntervalMs": 500
}
],
"name": "Zeppelin Tutorial",
"name": "Zeppelin Tutorial/Basic Features (Spark)",
"id": "2A94M5J1Z",
"angularObjects": {
"2B6FF8NNU": [],
@ -344,4 +344,4 @@
"looknfeel": "default"
},
"info": {}
}
}

View file

@ -1037,7 +1037,7 @@
"progressUpdateIntervalMs": 500
}
],
"name": "R Tutorial",
"name": "Zeppelin Tutorial/R (SparkR)",
"id": "2BWJFTXKJ",
"lastReplName": {
"value": ""
@ -1066,4 +1066,4 @@
"looknfeel": "default"
},
"info": {}
}
}

View file

@ -743,7 +743,7 @@
"progressUpdateIntervalMs": 500
}
],
"name": "Mahout Tutorial",
"name": "Zeppelin Tutorial/Using Mahout",
"id": "2BYEZ5EVK",
"angularObjects": {
"2BWWSVNY7:shared_process": [],
@ -769,4 +769,4 @@
},
"config": {},
"info": {}
}
}

View file

@ -669,7 +669,7 @@
"progressUpdateIntervalMs": 500
}
],
"name": "Zeppelin Tutorial: Python - matplotlib basic",
"name": "Zeppelin Tutorial/Matplotlib (Python • PySpark)",
"id": "2C2AUG798",
"angularObjects": {
"2BWWUCUVW:shared_process": [],
@ -693,4 +693,4 @@
},
"config": {},
"info": {}
}
}

View file

@ -0,0 +1,192 @@
/*
* 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.python;
import org.apache.zeppelin.interpreter.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Conda support
*/
public class PythonCondaInterpreter extends Interpreter {
Logger logger = LoggerFactory.getLogger(PythonCondaInterpreter.class);
Pattern condaEnvListPattern = Pattern.compile("([^\\s]*)[\\s*]*\\s(.*)");
Pattern listPattern = Pattern.compile("env\\s*list\\s?");
Pattern activatePattern = Pattern.compile("activate\\s*(.*)");
Pattern deactivatePattern = Pattern.compile("deactivate");
Pattern helpPattern = Pattern.compile("help");
String pythonCommand = null;
public PythonCondaInterpreter(Properties property) {
super(property);
}
@Override
public void open() {
}
@Override
public void close() {
}
@Override
public InterpreterResult interpret(String st, InterpreterContext context) {
InterpreterOutput out = context.out;
Matcher listMatcher = listPattern.matcher(st);
Matcher activateMatcher = activatePattern.matcher(st);
Matcher deactivateMatcher = deactivatePattern.matcher(st);
Matcher helpMatcher = helpPattern.matcher(st);
if (st == null || st.isEmpty() || listMatcher.matches()) {
listEnv(out);
return new InterpreterResult(InterpreterResult.Code.SUCCESS);
} else if (activateMatcher.matches()) {
String envName = activateMatcher.group(1);
pythonCommand = "conda run -n " + envName + " \"python -iu\"";
restartPythonProcess();
return new InterpreterResult(InterpreterResult.Code.SUCCESS, "\"" + envName + "\" activated");
} else if (deactivateMatcher.matches()) {
pythonCommand = null;
restartPythonProcess();
return new InterpreterResult(InterpreterResult.Code.SUCCESS, "Deactivated");
} else if (helpMatcher.matches()) {
printUsage(out);
return new InterpreterResult(InterpreterResult.Code.SUCCESS);
} else {
return new InterpreterResult(InterpreterResult.Code.ERROR, "Not supported command: " + st);
}
}
private void restartPythonProcess() {
PythonInterpreter python = getPythonInterpreter();
python.close();
python.open();
}
protected PythonInterpreter getPythonInterpreter() {
LazyOpenInterpreter lazy = null;
PythonInterpreter python = null;
Interpreter p = getInterpreterInTheSameSessionByClassName(PythonInterpreter.class.getName());
while (p instanceof WrappedInterpreter) {
if (p instanceof LazyOpenInterpreter) {
lazy = (LazyOpenInterpreter) p;
}
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
python = (PythonInterpreter) p;
if (lazy != null) {
lazy.open();
}
return python;
}
public String getPythonCommand() {
return pythonCommand;
}
private void listEnv(InterpreterOutput out) {
StringBuilder sb = createStringBuilder();
try {
int exit = runCommand(sb, "conda", "env", "list");
if (exit == 0) {
out.setType(InterpreterResult.Type.HTML);
out.write("<h4>Conda environments</h4>\n");
// start table
out.write("<div style=\"display:table\">\n");
String[] lines = sb.toString().split("\n");
for (String s : lines) {
if (s == null || s.isEmpty() || s.startsWith("#")) {
continue;
}
Matcher match = condaEnvListPattern.matcher(s);
if (!match.matches()) {
continue;
}
out.write(String.format("<div style=\"display:table-row\">" +
"<div style=\"display:table-cell;width:150px\">%s</div>" +
"<div style=\"display:table-cell;\">%s</div>" +
"</div>\n",
match.group(1), match.group(2)));
}
// end table
out.write("</div><br />\n");
out.write("<small><code>%python.conda help</code> for the usage</small>\n");
} else {
out.write("Failed to run 'conda' " + exit + "\n");
}
} catch (IOException | InterruptedException e) {
throw new InterpreterException(e);
}
}
private void printUsage(InterpreterOutput out) {
try {
out.setType(InterpreterResult.Type.HTML);
out.writeResource("output_templates/usage.html");
} catch (IOException e) {
logger.error("Can't print usage", e);
}
}
@Override
public void cancel(InterpreterContext context) {
}
@Override
public FormType getFormType() {
return FormType.NONE;
}
@Override
public int getProgress(InterpreterContext context) {
return 0;
}
protected int runCommand(StringBuilder sb, String ... command)
throws IOException, InterruptedException {
ProcessBuilder builder = new ProcessBuilder(command);
builder.redirectErrorStream(true);
Process process = builder.start();
InputStream stdout = process.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(stdout));
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
int r = process.waitFor(); // Let the process finish.
return r;
}
protected StringBuilder createStringBuilder() {
return new StringBuilder();
}
}

View file

@ -28,12 +28,9 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.zeppelin.display.GUI;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.*;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.apache.zeppelin.interpreter.InterpreterHookRegistry.HookType;
import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
import org.apache.zeppelin.scheduler.Job;
import org.apache.zeppelin.scheduler.Scheduler;
@ -123,6 +120,7 @@ public class PythonInterpreter extends Interpreter {
try {
if (process != null) {
process.close();
process = null;
}
if (gatewayServer != null) {
gatewayServer.shutdown();
@ -201,7 +199,12 @@ public class PythonInterpreter extends Interpreter {
public PythonProcess getPythonProcess() {
if (process == null) {
return new PythonProcess(getProperty(ZEPPELIN_PYTHON));
PythonCondaInterpreter conda = getCondaInterpreter();
String binPath = getProperty(ZEPPELIN_PYTHON);
if (conda != null && conda.getPythonCommand() != null) {
binPath = conda.getPythonCommand();
}
return new PythonProcess(binPath);
} else {
return process;
}
@ -281,5 +284,25 @@ public class PythonInterpreter extends Interpreter {
public int getMaxResult() {
return maxResult;
}
private PythonCondaInterpreter getCondaInterpreter() {
LazyOpenInterpreter lazy = null;
PythonCondaInterpreter conda = null;
Interpreter p = getInterpreterInTheSameSessionByClassName(
PythonCondaInterpreter.class.getName());
while (p instanceof WrappedInterpreter) {
if (p instanceof LazyOpenInterpreter) {
lazy = (LazyOpenInterpreter) p;
}
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
conda = (PythonCondaInterpreter) p;
if (lazy != null) {
lazy.open();
}
return conda;
}
}

View file

@ -49,7 +49,23 @@ public class PythonProcess {
}
public void open() throws IOException {
ProcessBuilder builder = new ProcessBuilder(binPath, "-iu");
ProcessBuilder builder;
boolean hasParams = binPath.split(" ").length > 1;
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
if (hasParams) {
builder = new ProcessBuilder(binPath.split(" "));
} else {
builder = new ProcessBuilder(binPath, "-iu");
}
} else {
String cmd;
if (hasParams) {
cmd = binPath;
} else {
cmd = binPath + " -iu";
}
builder = new ProcessBuilder("bash", "-c", cmd);
}
builder.redirectErrorStream(true);
process = builder.start();

View file

@ -32,5 +32,16 @@
"language": "sql",
"editOnDblClick": false
}
},
{
"group": "python",
"name": "conda",
"className": "org.apache.zeppelin.python.PythonCondaInterpreter",
"properties": {
},
"editor":{
"language": "sh",
"editOnDblClick": false
}
}
]

View file

@ -0,0 +1,27 @@
<!--
Licensed 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.
-->
<h4>Usage</h4>
<div>
Activate an environment (python interpreter will be restarted)
<pre>%python.conda activate [ENV NAME]</pre>
</div>
<div>
Deactivate
<pre>%python.conda deactivate</pre>
</div>
<div>
List the Conda environments
<pre>%python.conda</pre>
</div>

View file

@ -0,0 +1,116 @@
/*
* 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.python;
import org.apache.zeppelin.display.GUI;
import org.apache.zeppelin.interpreter.*;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
public class PythonCondaInterpreterTest implements InterpreterOutputListener {
private PythonCondaInterpreter conda;
private PythonInterpreter python;
@Before
public void setUp() {
conda = spy(new PythonCondaInterpreter(new Properties()));
python = mock(PythonInterpreter.class);
InterpreterGroup group = new InterpreterGroup();
group.put("note", Arrays.asList(python, conda));
python.setInterpreterGroup(group);
conda.setInterpreterGroup(group);
doReturn(python).when(conda).getPythonInterpreter();
}
@Test
public void testListEnv() throws IOException, InterruptedException {
InterpreterContext context = getInterpreterContext();
StringBuilder sb = new StringBuilder();
sb.append("#comment\n\nenv1 * /path1\nenv2\t/path2\n");
doReturn(sb).when(conda).createStringBuilder();
doReturn(0).when(conda)
.runCommand(any(StringBuilder.class), anyString(), anyString(), anyString());
// list available env
InterpreterResult result = conda.interpret("", context);
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
String out = new String(context.out.toByteArray());
assertTrue(out.contains(">env1<"));
assertTrue(out.contains(">/path1<"));
assertTrue(out.contains(">env2<"));
assertTrue(out.contains(">/path2<"));
}
@Test
public void testActivateEnv() {
InterpreterContext context = getInterpreterContext();
conda.interpret("activate env", context);
verify(python, times(1)).open();
verify(python, times(1)).close();
assertEquals("conda run -n env \"python -iu\"", conda.getPythonCommand());
}
@Test
public void testDeactivate() {
InterpreterContext context = getInterpreterContext();
conda.interpret("deactivate", context);
verify(python, times(1)).open();
verify(python, times(1)).close();
assertEquals(null, conda.getPythonCommand());
}
private InterpreterContext getInterpreterContext() {
return new InterpreterContext(
"noteId",
"paragraphId",
"paragraphTitle",
"paragraphText",
new AuthenticationInfo(),
new HashMap<String, Object>(),
new GUI(),
null,
null,
null,
new InterpreterOutput(this));
}
@Override
public void onAppend(InterpreterOutput out, byte[] line) {
}
@Override
public void onUpdate(InterpreterOutput out, byte[] output) {
}
}

View file

@ -37,9 +37,13 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.LinkedList;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import org.apache.zeppelin.interpreter.ClassloaderInterpreter;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.junit.Before;
import org.junit.Test;
@ -82,8 +86,15 @@ public class PythonInterpreterTest {
}
});
// python interpreter
pythonInterpreter = spy(new PythonInterpreter(getPythonTestProperties()));
// create interpreter group
InterpreterGroup group = new InterpreterGroup();
group.put("note", new LinkedList<Interpreter>());
group.get("note").add(pythonInterpreter);
pythonInterpreter.setInterpreterGroup(group);
when(pythonInterpreter.getPythonProcess()).thenReturn(mockPythonProcess);
when(mockPythonProcess.sendAndGetResult(eq("\n\nimport py4j\n"))).thenReturn("ImportError");
}
@ -220,15 +231,24 @@ public class PythonInterpreterTest {
@Test
public void checkMultiRowErrorFails() {
PythonInterpreter pythonInterpreter = new PythonInterpreter(
PythonInterpreterTest.getPythonTestProperties()
);
// create interpreter group
InterpreterGroup group = new InterpreterGroup();
group.put("note", new LinkedList<Interpreter>());
group.get("note").add(pythonInterpreter);
pythonInterpreter.setInterpreterGroup(group);
pythonInterpreter.open();
String codeRaiseException = "raise Exception(\"test exception\")";
InterpreterResult ret = pythonInterpreter.interpret(codeRaiseException, null);
assertNotNull("Interpreter result for raise exception is Null", ret);
System.err.println("ret = '" + ret + "'");
assertEquals(InterpreterResult.Code.ERROR, ret.code());
assertTrue(ret.message().length() > 0);

View file

@ -21,9 +21,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.junit.Test;
import java.util.Arrays;
/**
* Python interpreter unit test that user real Python
*
@ -46,6 +50,11 @@ public class PythonInterpreterWithPythonInstalledTest {
//given
PythonInterpreter realPython = new PythonInterpreter(
PythonInterpreterTest.getPythonTestProperties());
// create interpreter group
InterpreterGroup group = new InterpreterGroup();
group.put("note", Arrays.asList((Interpreter) realPython));
realPython.setInterpreterGroup(group);
realPython.open();
//when
@ -65,6 +74,9 @@ public class PythonInterpreterWithPythonInstalledTest {
//given
PythonInterpreter realPython = new PythonInterpreter(
PythonInterpreterTest.getPythonTestProperties());
InterpreterGroup group = new InterpreterGroup();
group.put("note", Arrays.asList((Interpreter) realPython));
realPython.setInterpreterGroup(group);
realPython.open();
//when
@ -84,6 +96,9 @@ public class PythonInterpreterWithPythonInstalledTest {
//given
PythonInterpreter realPython = new PythonInterpreter(
PythonInterpreterTest.getPythonTestProperties());
InterpreterGroup group = new InterpreterGroup();
group.put("note", Arrays.asList((Interpreter) realPython));
realPython.setInterpreterGroup(group);
realPython.open();
//when

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

@ -37,6 +37,7 @@ public class InterpreterOption {
boolean isExistingProcess;
boolean setPermission;
List<String> users;
boolean isUserImpersonate;
public boolean isExistingProcess() {
return isExistingProcess;
@ -66,6 +67,14 @@ public class InterpreterOption {
return users;
}
public boolean isUserImpersonate() {
return isUserImpersonate;
}
public void setUserImpersonate(boolean userImpersonate) {
isUserImpersonate = userImpersonate;
}
public InterpreterOption() {
this(false);
}

View file

@ -59,6 +59,8 @@ public class RemoteInterpreter extends Interpreter {
private int maxPoolSize;
private String host;
private int port;
private String userName;
private Boolean isUserImpersonate;
/**
* Remote interpreter and manage interpreter process
@ -72,7 +74,9 @@ public class RemoteInterpreter extends Interpreter {
int connectTimeout,
int maxPoolSize,
RemoteInterpreterProcessListener remoteInterpreterProcessListener,
ApplicationEventListener appListener) {
ApplicationEventListener appListener,
String userName,
Boolean isUserImpersonate) {
super(property);
this.noteId = noteId;
this.className = className;
@ -85,6 +89,8 @@ public class RemoteInterpreter extends Interpreter {
this.maxPoolSize = maxPoolSize;
this.remoteInterpreterProcessListener = remoteInterpreterProcessListener;
this.applicationEventListener = appListener;
this.userName = userName;
this.isUserImpersonate = isUserImpersonate;
}
@ -100,7 +106,9 @@ public class RemoteInterpreter extends Interpreter {
int connectTimeout,
int maxPoolSize,
RemoteInterpreterProcessListener remoteInterpreterProcessListener,
ApplicationEventListener appListener) {
ApplicationEventListener appListener,
String userName,
Boolean isUserImpersonate) {
super(property);
this.noteId = noteId;
this.className = className;
@ -111,6 +119,8 @@ public class RemoteInterpreter extends Interpreter {
this.maxPoolSize = maxPoolSize;
this.remoteInterpreterProcessListener = remoteInterpreterProcessListener;
this.applicationEventListener = appListener;
this.userName = userName;
this.isUserImpersonate = isUserImpersonate;
}
@ -125,7 +135,9 @@ public class RemoteInterpreter extends Interpreter {
Map<String, String> env,
int connectTimeout,
RemoteInterpreterProcessListener remoteInterpreterProcessListener,
ApplicationEventListener appListener) {
ApplicationEventListener appListener,
String userName,
Boolean isUserImpersonate) {
super(property);
this.className = className;
this.noteId = noteId;
@ -138,6 +150,8 @@ public class RemoteInterpreter extends Interpreter {
this.maxPoolSize = 10;
this.remoteInterpreterProcessListener = remoteInterpreterProcessListener;
this.applicationEventListener = appListener;
this.userName = userName;
this.isUserImpersonate = isUserImpersonate;
}
private Map<String, String> getEnvFromInterpreterProperty(Properties property) {
@ -205,7 +219,7 @@ public class RemoteInterpreter extends Interpreter {
RemoteInterpreterProcess interpreterProcess = getInterpreterProcess();
final InterpreterGroup interpreterGroup = getInterpreterGroup();
interpreterProcess.reference(interpreterGroup);
interpreterProcess.reference(interpreterGroup, userName, isUserImpersonate);
interpreterProcess.setMaxPoolSize(
Math.max(this.maxPoolSize, interpreterProcess.getMaxPoolSize()));
String groupId = interpreterGroup.getId();

View file

@ -88,7 +88,7 @@ public class RemoteInterpreterManagedProcess extends RemoteInterpreterProcess
}
@Override
public void start() {
public void start(String userName, Boolean isUserImpersonate) {
// start server process
try {
port = RemoteInterpreterUtils.findRandomAvailablePortOnAllLocalInterfaces();
@ -101,6 +101,10 @@ public class RemoteInterpreterManagedProcess extends RemoteInterpreterProcess
cmdLine.addArgument(interpreterDir, false);
cmdLine.addArgument("-p", false);
cmdLine.addArgument(Integer.toString(port), false);
if (isUserImpersonate && !userName.equals("anonymous")) {
cmdLine.addArgument("-u", false);
cmdLine.addArgument(userName, false);
}
cmdLine.addArgument("-l", false);
cmdLine.addArgument(localRepoDir, false);

View file

@ -63,7 +63,7 @@ public abstract class RemoteInterpreterProcess {
public abstract String getHost();
public abstract int getPort();
public abstract void start();
public abstract void start(String userName, Boolean isUserImpersonate);
public abstract void stop();
public abstract boolean isRunning();
@ -71,10 +71,11 @@ public abstract class RemoteInterpreterProcess {
return connectTimeout;
}
public int reference(InterpreterGroup interpreterGroup) {
public int reference(InterpreterGroup interpreterGroup, String userName,
Boolean isUserImpersonate) {
synchronized (referenceCount) {
if (!isRunning()) {
start();
start(userName, isUserImpersonate);
}
if (clientPool == null) {

View file

@ -51,7 +51,7 @@ public class RemoteInterpreterRunningProcess extends RemoteInterpreterProcess {
}
@Override
public void start() {
public void start(String userName, Boolean isUserImpersonate) {
// assume process is externally managed. nothing to do
}

View file

@ -74,7 +74,9 @@ public class RemoteAngularObjectTest implements AngularObjectRegistryListener {
env,
10 * 1000,
null,
null
null,
"anonymous",
false
);
intpGroup.put("note", new LinkedList<Interpreter>());

View file

@ -72,7 +72,9 @@ public class RemoteInterpreterOutputTestStream implements RemoteInterpreterProce
env,
10 * 1000,
this,
null);
null,
"anonymous",
false);
intpGroup.get("note").add(intp);
intp.setInterpreterGroup(intpGroup);

View file

@ -46,8 +46,8 @@ public class RemoteInterpreterProcessTest {
10 * 1000, null, null);
assertFalse(rip.isRunning());
assertEquals(0, rip.referenceCount());
assertEquals(1, rip.reference(intpGroup));
assertEquals(2, rip.reference(intpGroup));
assertEquals(1, rip.reference(intpGroup, "anonymous", false));
assertEquals(2, rip.reference(intpGroup, "anonymous", false));
assertEquals(true, rip.isRunning());
assertEquals(1, rip.dereference());
assertEquals(true, rip.isRunning());
@ -61,7 +61,7 @@ public class RemoteInterpreterProcessTest {
RemoteInterpreterManagedProcess rip = new RemoteInterpreterManagedProcess(
INTERPRETER_SCRIPT, "nonexists", "fakeRepo", new HashMap<String, String>(),
mock(RemoteInterpreterEventPoller.class), 10 * 1000);
rip.reference(intpGroup);
rip.reference(intpGroup, "anonymous", false);
assertEquals(0, rip.getNumActiveClient());
assertEquals(0, rip.getNumIdleClient());
@ -106,7 +106,7 @@ public class RemoteInterpreterProcessTest {
, 10 * 1000);
assertFalse(rip.isRunning());
assertEquals(0, rip.referenceCount());
assertEquals(1, rip.reference(intpGroup));
assertEquals(1, rip.reference(intpGroup, "anonymous", false));
assertEquals(true, rip.isRunning());
}
}

View file

@ -90,7 +90,9 @@ public class RemoteInterpreterTest {
env,
10 * 1000,
null,
null);
null,
"anonymous",
false);
}
private RemoteInterpreter createMockInterpreterB(Properties p) {
@ -108,7 +110,9 @@ public class RemoteInterpreterTest {
env,
10 * 1000,
null,
null);
null,
"anonymous",
false);
}
@Test
@ -209,7 +213,9 @@ public class RemoteInterpreterTest {
env,
10 * 1000,
null,
null);
null,
"anonymous",
false);
intpGroup.get("note").add(intpA);
@ -225,7 +231,9 @@ public class RemoteInterpreterTest {
env,
10 * 1000,
null,
null);
null,
"anonymous",
false);
intpGroup.get("note").add(intpB);
intpB.setInterpreterGroup(intpGroup);
@ -696,7 +704,8 @@ public class RemoteInterpreterTest {
//Given
final Client client = Mockito.mock(Client.class);
final RemoteInterpreter intr = new RemoteInterpreter(new Properties(), "noteId",
MockInterpreterA.class.getName(), "runner", "path","localRepo", env, 10 * 1000, null, null);
MockInterpreterA.class.getName(), "runner", "path", "localRepo", env, 10 * 1000, null,
null, "anonymous", false);
final AngularObjectRegistry registry = new AngularObjectRegistry("spark", null);
registry.add("name", "DuyHai DOAN", "nodeId", "paragraphId");
final InterpreterGroup interpreterGroup = new InterpreterGroup("groupId");
@ -742,7 +751,9 @@ public class RemoteInterpreterTest {
env,
10 * 1000,
null,
null);
null,
"anonymous",
false);
intpGroup.put("note", new LinkedList<Interpreter>());
intpGroup.get("note").add(intp);

View file

@ -70,7 +70,9 @@ public class DistributedResourcePoolTest {
env,
10 * 1000,
null,
null
null,
"anonymous",
false
);
intpGroup1 = new InterpreterGroup("intpGroup1");
@ -88,7 +90,9 @@ public class DistributedResourcePoolTest {
env,
10 * 1000,
null,
null
null,
"anonymous",
false
);
intpGroup2 = new InterpreterGroup("intpGroup2");

View file

@ -81,7 +81,9 @@ public class RemoteSchedulerTest implements RemoteInterpreterProcessListener {
env,
10 * 1000,
this,
null);
null,
"anonymous",
false);
intpGroup.put("note", new LinkedList<Interpreter>());
intpGroup.get("note").add(intpA);
@ -171,7 +173,9 @@ public class RemoteSchedulerTest implements RemoteInterpreterProcessListener {
env,
10 * 1000,
this,
null);
null,
"anonymous",
false);
intpGroup.put("note", new LinkedList<Interpreter>());
intpGroup.get("note").add(intpA);

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

@ -568,6 +568,20 @@ public class NotebookServer extends WebSocketServlet implements
broadcast(noteId, new Message(OP.INTERPRETER_BINDINGS).put("interpreterBindings", settingList));
}
public void broadcastParagraph(Note note, Paragraph p) {
broadcast(note.getId(), new Message(OP.PARAGRAPH).put("paragraph", p));
}
private void broadcastNewParagraph(Note note, Paragraph para) {
LOG.info("Broadcasting paragraph on run call instead of note.");
int paraIndex = note.getParagraphs().indexOf(para);
broadcast(
note.getId(),
new Message(OP.PARAGRAPH_ADDED)
.put("paragraph", para)
.put("index", paraIndex));
}
public void broadcastNoteList(AuthenticationInfo subject, HashSet userAndRoles) {
if (subject == null) {
subject = new AuthenticationInfo(StringUtils.EMPTY);
@ -715,7 +729,10 @@ public class NotebookServer extends WebSocketServlet implements
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
note.persist(subject);
broadcastNote(note);
broadcast(note.getId(), new Message(OP.NOTE_UPDATED)
.put("name", name)
.put("config", config)
.put("info", note.getInfo()));
broadcastNoteList(subject, userAndRoles);
}
}
@ -857,7 +874,7 @@ public class NotebookServer extends WebSocketServlet implements
p.setTitle((String) fromMessage.get("title"));
p.setText((String) fromMessage.get("paragraph"));
note.persist(subject);
broadcast(note.getId(), new Message(OP.PARAGRAPH).put("paragraph", p));
broadcastParagraph(note, p);;
}
private void cloneNote(NotebookSocket conn, HashSet<String> userAndRoles,
@ -930,9 +947,12 @@ public class NotebookServer extends WebSocketServlet implements
/** We dont want to remove the last paragraph */
if (!note.isLastParagraph(paragraphId)) {
note.removeParagraph(subject.getUser(), paragraphId);
Paragraph para = note.removeParagraph(subject.getUser(), paragraphId);
note.persist(subject);
broadcastNote(note);
if (para != null) {
broadcast(note.getId(), new Message(OP.PARAGRAPH_REMOVED).
put("id", para.getId()));
}
}
}
@ -950,9 +970,9 @@ public class NotebookServer extends WebSocketServlet implements
userAndRoles, notebookAuthorization.getWriters(noteId));
return;
}
note.clearParagraphOutput(paragraphId);
broadcastNote(note);
Paragraph paragraph = note.getParagraph(paragraphId);
broadcastParagraph(note, paragraph);
}
private void completion(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
@ -1237,7 +1257,9 @@ public class NotebookServer extends WebSocketServlet implements
note.moveParagraph(paragraphId, newIndex);
note.persist(subject);
broadcastNote(note);
broadcast(note.getId(), new Message(OP.PARAGRAPH_MOVED)
.put("id", paragraphId)
.put("index", newIndex));
}
private void insertParagraph(NotebookSocket conn, HashSet<String> userAndRoles,
@ -1254,9 +1276,9 @@ public class NotebookServer extends WebSocketServlet implements
return;
}
note.insertParagraph(index);
Paragraph newPara = note.insertParagraph(index);
note.persist(subject);
broadcastNote(note);
broadcastNewParagraph(note, newPara);
}
private void cancelParagraph(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
@ -1313,7 +1335,8 @@ public class NotebookServer extends WebSocketServlet implements
boolean isTheLastParagraph = note.isLastParagraph(p.getId());
if (!(text.trim().equals(p.getMagic()) || Strings.isNullOrEmpty(text)) &&
isTheLastParagraph) {
note.addParagraph();
Paragraph newPara = note.addParagraph();
broadcastNewParagraph(note, newPara);
}
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
@ -1631,8 +1654,9 @@ public class NotebookServer extends WebSocketServlet implements
LOG.error(e.toString(), e);
}
}
notebookServer.broadcastNote(note);
if (job instanceof Paragraph) {
notebookServer.broadcastParagraph(note, (Paragraph) job);
}
try {
notebookServer.broadcastUpdateNoteJobInfo(System.currentTimeMillis() - 5000);
} catch (IOException e) {

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

@ -439,7 +439,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
Actions action = new Actions(driver);
waitForParagraph(1, "READY");
pollingWait(By.xpath(getParagraphXPath(1) + "//textarea"), MAX_PARAGRAPH_TIMEOUT_SEC);
driver.findElement(By.xpath(getParagraphXPath(1) + "//textarea")).sendKeys(Keys.SHIFT + "5");
driver.findElement(By.xpath(getParagraphXPath(1) + "//textarea")).sendKeys("md" + Keys.ENTER);
driver.findElement(By.xpath(getParagraphXPath(1) + "//textarea")).sendKeys(Keys.SHIFT + "3");

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

@ -196,7 +196,20 @@ limitations under the License.
</div>
</div>
</div>
<div class="row interpreter" style="margin-top: 5px;">
<div class="row interpreter" style="margin-top: 5px;"
ng-show="getInterpreterRunningOption(setting.id)=='Per User' && getPerUserOption(setting.id)=='isolated'">
<div class="col-md-12">
<div class="checkbox remove-margin-top-bottom">
<span class="input-group" style="line-height:30px;">
<label>
<input type="checkbox" style="width:20px" ng-model="setting.option.isUserImpersonate" />
User Impersonate
</label>
</span>
</div>
</div>
</div>
<div class="row interpreter">
<div class="col-md-12">
<div class="checkbox remove-margin-top-bottom">
<span class="input-group" style="line-height:30px;">

View file

@ -327,6 +327,13 @@
if (setting.option.setPermission === undefined) {
setting.option.setPermission = false;
}
if (setting.option.isUserImpersonate === undefined) {
setting.option.isUserImpersonate = false;
}
if (!($scope.getInterpreterRunningOption(settingId) === 'Per User' &&
$scope.getPerUserOption(settingId) === 'isolated')) {
setting.option.isUserImpersonate = false;
}
if (setting.option.remote === undefined) {
// remote always true for now
setting.option.remote = true;

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">
@ -313,7 +313,23 @@ limitations under the License.
</div>
</div>
</div>
<div class="row interpreter" style="margin-top: 5px;">
<div class="row interpreter" style="margin-top: 5px;"
ng-show="getInterpreterRunningOption(setting.id)=='Per User' && getPerUserOption(setting.id)=='isolated'">
<div class="col-md-12">
<div class="checkbox remove-margin-top-bottom">
<span class="input-group" style="line-height:30px;">
<label>
<input type="checkbox" style="width:20px"
ng-model="setting.option.isUserImpersonate"
ng-disabled="!valueform.$visible" />
User Impersonate
</label>
</span>
</div>
</div>
</div>
<div class="row interpreter">
<div class="col-md-12">
<div class="checkbox remove-margin-top-bottom">
<span class="input-group" style="line-height:30px;">

View file

@ -360,87 +360,51 @@
return noteCopy;
};
var updateNote = function(note) {
/** update Note name */
if (note.name !== $scope.note.name) {
console.log('change note name: %o to %o', $scope.note.name, note.name);
$scope.note.name = note.name;
}
$scope.note.config = note.config;
$scope.note.info = note.info;
var newParagraphIds = note.paragraphs.map(function(x) {return x.id;});
var oldParagraphIds = $scope.note.paragraphs.map(function(x) {return x.id;});
var numNewParagraphs = newParagraphIds.length;
var numOldParagraphs = oldParagraphIds.length;
var paragraphToBeFocused;
var focusedParagraph;
for (var i = 0; i < $scope.note.paragraphs.length; i++) {
var paragraphId = $scope.note.paragraphs[i].id;
if (angular.element('#' + paragraphId + '_paragraphColumn_main').scope().paragraphFocused) {
focusedParagraph = paragraphId;
break;
var addPara = function(paragraph, index) {
$scope.note.paragraphs.splice(index, 0, paragraph);
_.each($scope.note.paragraphs, function(para) {
if (para.id === paragraph.id) {
para.focus = true;
}
}
/** add a new paragraph */
if (numNewParagraphs > numOldParagraphs) {
for (var index in newParagraphIds) {
if (oldParagraphIds[index] !== newParagraphIds[index]) {
$scope.note.paragraphs.splice(index, 0, note.paragraphs[index]);
paragraphToBeFocused = note.paragraphs[index].id;
break;
}
$scope.$broadcast('updateParagraph', {
note: $scope.note, // pass the note object to paragraph scope
paragraph: note.paragraphs[index]});
}
}
/** update or move paragraph */
if (numNewParagraphs === numOldParagraphs) {
for (var idx in newParagraphIds) {
var newEntry = note.paragraphs[idx];
if (oldParagraphIds[idx] === newParagraphIds[idx]) {
$scope.$broadcast('updateParagraph', {
note: $scope.note, // pass the note object to paragraph scope
paragraph: newEntry});
} else {
// move paragraph
var oldIdx = oldParagraphIds.indexOf(newParagraphIds[idx]);
$scope.note.paragraphs.splice(oldIdx, 1);
$scope.note.paragraphs.splice(idx, 0, newEntry);
// rebuild id list since paragraph has moved.
oldParagraphIds = $scope.note.paragraphs.map(function(x) {return x.id;});
}
if (focusedParagraph === newParagraphIds[idx]) {
paragraphToBeFocused = focusedParagraph;
}
}
}
/** remove paragraph */
if (numNewParagraphs < numOldParagraphs) {
for (var oldidx in oldParagraphIds) {
if (oldParagraphIds[oldidx] !== newParagraphIds[oldidx]) {
$scope.note.paragraphs.splice(oldidx, 1);
break;
}
}
}
// restore focus of paragraph
for (var f = 0; f < $scope.note.paragraphs.length; f++) {
if (paragraphToBeFocused === $scope.note.paragraphs[f].id) {
$scope.note.paragraphs[f].focus = true;
}
}
});
};
var removePara = function(paragraphId) {
var removeIdx;
_.each($scope.note.paragraphs, function(para, idx) {
if (para.id === paragraphId) {
removeIdx = idx;
}
});
return $scope.note.paragraphs.splice(removeIdx, 1);
};
$scope.$on('addParagraph', function(event, paragraph, index) {
addPara(paragraph, index);
});
$scope.$on('removeParagraph', function(event, paragraphId) {
removePara(paragraphId);
});
$scope.$on('moveParagraph', function(event, paragraphId, newIdx) {
var removedPara = removePara(paragraphId);
if (removedPara && removedPara.length === 1) {
addPara(removedPara[0], newIdx);
}
});
$scope.$on('updateNote', function(event, name, config, info) {
/** update Note name */
if (name !== $scope.note.name) {
console.log('change note name to : %o', $scope.note.name);
$scope.note.name = name;
}
$scope.note.config = config;
$scope.note.info = info;
initializeLookAndFeel();
});
var getInterpreterBindings = function() {
websocketMsgSrv.getInterpreterBindings($scope.note.id);
};
@ -856,8 +820,6 @@
if ($scope.note === null) {
$scope.note = note;
} else {
updateNote(note);
}
initializeLookAndFeel();
//open interpreter binding setting when there're none selected

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

@ -17,11 +17,12 @@
angular.module('zeppelinWebApp').controller('LoginCtrl', LoginCtrl);
LoginCtrl.$inject = ['$scope', '$rootScope', '$http', '$httpParamSerializer', 'baseUrlSrv'];
function LoginCtrl($scope, $rootScope, $http, $httpParamSerializer, baseUrlSrv) {
$scope.SigningIn = false;
$scope.loginParams = {};
$scope.login = function() {
$scope.SigningIn = true;
$http({
method: 'POST',
url: baseUrlSrv.getRestApiBase() + '/login',
@ -39,6 +40,7 @@
$rootScope.userName = $scope.loginParams.userName;
}, function errorCallback(errorResponse) {
$scope.loginParams.errorText = 'The username and password that you entered don\'t match.';
$scope.SigningIn = false;
});
};

View file

@ -41,8 +41,11 @@ limitations under the License.
</div>
</div>
<div class="modal-footer">
<div>
<div class="modal-footer" ng-switch on="SigningIn">
<div ng-switch-when="true">
<button type="button" class="btn btn-default btn-primary" disabled><i class="fa fa-circle-o-notch fa-spin"></i> Signing In</button>
</div>
<div ng-switch-default>
<button type="button" class="btn btn-default btn-primary" ng-click="login()">Login</button>
</div>
</div>

View file

@ -12,11 +12,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<a class="notebook-list-item" ng-if="note.id" href="#/notebook/{{note.id}}">
<a class="notebook-list-item" ng-if="!note.children" href="#/notebook/{{note.id}}">
<i style="font-size: 10px; margin-right: 5px;" class="icon-doc"></i>
<span>{{noteName(note)}}</span>
</a>
<li ng-if="!note.id" ng-click="$event.stopPropagation()">
<li ng-if="note.children" ng-click="$event.stopPropagation()">
<expand-collapse>
<div>
<a class="notebook-list-item" href="javascript:void(0)">

View file

@ -140,6 +140,14 @@
$rootScope.$broadcast('configurationsInfo', data);
} else if (op === 'INTERPRETER_SETTINGS') {
$rootScope.$broadcast('interpreterSettings', data);
} else if (op === 'PARAGRAPH_ADDED') {
$rootScope.$broadcast('addParagraph', data.paragraph, data.index);
} else if (op === 'PARAGRAPH_REMOVED') {
$rootScope.$broadcast('removeParagraph', data.id);
} else if (op === 'PARAGRAPH_MOVED') {
$rootScope.$broadcast('moveParagraph', data.id, data.index);
} else if (op === 'NOTE_UPDATED') {
$rootScope.$broadcast('updateNote', data.name, data.config, data.info);
}
});

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);
}
@ -533,6 +537,7 @@ public class ZeppelinConfiguration extends XMLConfiguration {
+ "org.apache.zeppelin.flink.FlinkInterpreter,"
+ "org.apache.zeppelin.python.PythonInterpreter,"
+ "org.apache.zeppelin.python.PythonInterpreterPandasSql,"
+ "org.apache.zeppelin.python.PythonCondaInterpreter,"
+ "org.apache.zeppelin.ignite.IgniteInterpreter,"
+ "org.apache.zeppelin.ignite.IgniteSqlInterpreter,"
+ "org.apache.zeppelin.lens.LensInterpreter,"
@ -570,6 +575,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

@ -776,10 +776,10 @@ public class InterpreterFactory implements InterpreterGroupFactory {
if (option.isExistingProcess()) {
interpreter =
connectToRemoteRepl(noteId, info.getClassName(), option.getHost(), option.getPort(),
properties);
properties, user, option.isUserImpersonate);
} else {
interpreter = createRemoteRepl(path, key, info.getClassName(), properties,
interpreterSetting.getId());
interpreterSetting.getId(), user, option.isUserImpersonate());
}
} else {
interpreter = createRepl(interpreterSetting.getPath(), info.getClassName(), properties);
@ -1100,17 +1100,18 @@ public class InterpreterFactory implements InterpreterGroupFactory {
}
private Interpreter connectToRemoteRepl(String noteId, String className, String host, int port,
Properties property) {
Properties property, String userName, Boolean isUserImpersonate) {
int connectTimeout = conf.getInt(ConfVars.ZEPPELIN_INTERPRETER_CONNECT_TIMEOUT);
int maxPoolSize = conf.getInt(ConfVars.ZEPPELIN_INTERPRETER_MAX_POOL_SIZE);
LazyOpenInterpreter intp = new LazyOpenInterpreter(
new RemoteInterpreter(property, noteId, className, host, port, connectTimeout, maxPoolSize,
remoteInterpreterProcessListener, appEventListener));
remoteInterpreterProcessListener, appEventListener, userName, isUserImpersonate));
return intp;
}
private Interpreter createRemoteRepl(String interpreterPath, String noteId, String className,
Properties property, String interpreterSettingId) {
Properties property, String interpreterSettingId, String userName,
Boolean isUserImpersonate) {
int connectTimeout = conf.getInt(ConfVars.ZEPPELIN_INTERPRETER_CONNECT_TIMEOUT);
String localRepoPath = conf.getInterpreterLocalRepoPath() + "/" + interpreterSettingId;
int maxPoolSize = conf.getInt(ConfVars.ZEPPELIN_INTERPRETER_MAX_POOL_SIZE);
@ -1118,7 +1119,7 @@ public class InterpreterFactory implements InterpreterGroupFactory {
RemoteInterpreter remoteInterpreter =
new RemoteInterpreter(property, noteId, className, conf.getInterpreterRemoteRunnerPath(),
interpreterPath, localRepoPath, connectTimeout, maxPoolSize,
remoteInterpreterProcessListener, appEventListener);
remoteInterpreterProcessListener, appEventListener, userName, isUserImpersonate);
remoteInterpreter.addEnv(env);
return new LazyOpenInterpreter(remoteInterpreter);
@ -1414,7 +1415,7 @@ public class InterpreterFactory implements InterpreterGroupFactory {
InterpreterGroup interpreterGroup = createInterpreterGroup("dev", option);
devInterpreter = connectToRemoteRepl("dev", DevInterpreter.class.getName(), "localhost",
ZeppelinDevServer.DEFAULT_TEST_INTERPRETER_PORT, new Properties());
ZeppelinDevServer.DEFAULT_TEST_INTERPRETER_PORT, new Properties(), "anonymous", false);
LinkedList<Interpreter> intpList = new LinkedList<>();
intpList.add(devInterpreter);

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

@ -160,24 +160,25 @@ public class Paragraph extends Job implements Serializable, Cloneable {
return null;
}
String trimmed = text.trim();
if (!trimmed.startsWith("%")) {
return null;
}
// get script head
int scriptHeadIndex = 0;
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (Character.isWhitespace(ch) || ch == '(') {
scriptHeadIndex = i;
for (int i = 0; i < trimmed.length(); i++) {
char ch = trimmed.charAt(i);
if (Character.isWhitespace(ch) || ch == '(' || ch == '\n') {
break;
}
scriptHeadIndex = i;
}
if (scriptHeadIndex == 0) {
return null;
}
String head = text.substring(0, scriptHeadIndex);
if (head.startsWith("%")) {
return head.substring(1);
} else {
if (scriptHeadIndex < 1) {
return null;
}
String head = text.substring(1, scriptHeadIndex + 1);
return head;
}
public String getScriptBody() {
@ -193,10 +194,12 @@ public class Paragraph extends Job implements Serializable, Cloneable {
if (magic == null) {
return text;
}
if (magic.length() + 1 >= text.length()) {
String trimmed = text.trim();
if (magic.length() + 1 >= trimmed.length()) {
return "";
}
return text.substring(magic.length() + 1).trim();
return trimmed.substring(magic.length() + 1).trim();
}
public Interpreter getRepl(String name) {
@ -594,7 +597,7 @@ public class Paragraph extends Job implements Serializable, Cloneable {
}
private boolean isValidInterpreter(String replName) {
return factory.getInterpreter("",
return factory.getInterpreter(user,
note.getId(), replName) != null;
}
}

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

@ -147,6 +147,10 @@ public class Message {
INTERPRETER_SETTINGS, // [s-c] interpreter settings
ERROR_INFO, // [s-c] error information to be sent
WATCHER, // [s-c] Change websocket to watcher mode.
PARAGRAPH_ADDED, // [s-c] paragraph is added
PARAGRAPH_REMOVED, // [s-c] paragraph deleted
PARAGRAPH_MOVED, // [s-c] paragraph moved
NOTE_UPDATED // [s-c] paragraph updated(name, config)
}
public static final Message EMPTY = new Message(null);

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

@ -52,6 +52,21 @@ public class ParagraphTest {
assertEquals(text, Paragraph.getScriptBody(text));
}
@Test
public void replNameAndNoBody() {
String text = "%md";
assertEquals("md", Paragraph.getRequiredReplName(text));
assertEquals("", Paragraph.getScriptBody(text));
}
@Test
public void replSingleCharName() {
String text = "%r a";
assertEquals("r", Paragraph.getRequiredReplName(text));
assertEquals("a", Paragraph.getScriptBody(text));
}
@Test
public void replNameEndsWithWhitespace() {
String text = "%md\r\n###Hello";

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>