mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
Merge branch 'master' into jdbc-impersonation
This commit is contained in:
commit
63f5ea7722
72 changed files with 2100 additions and 268 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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=$!
|
||||
|
|
|
|||
|
|
@ -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}'
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
66
docs/manual/processenduser.md
Normal file
66
docs/manual/processenduser.md
Normal 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
|
||||
```
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -32,5 +32,16 @@
|
|||
"language": "sql",
|
||||
"editOnDblClick": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": "python",
|
||||
"name": "conda",
|
||||
"className": "org.apache.zeppelin.python.PythonCondaInterpreter",
|
||||
"properties": {
|
||||
},
|
||||
"editor":{
|
||||
"language": "sh",
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
27
python/src/main/resources/output_templates/usage.html
Normal file
27
python/src/main/resources/output_templates/usage.html
Normal 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>
|
||||
|
||||
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<>();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,9 @@ public class RemoteAngularObjectTest implements AngularObjectRegistryListener {
|
|||
env,
|
||||
10 * 1000,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
"anonymous",
|
||||
false
|
||||
);
|
||||
|
||||
intpGroup.put("note", new LinkedList<Interpreter>());
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<>();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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=" Filter" ng-model="$parent.query.q" />
|
||||
<input type="text" class="note-name-query form-control" ng-click="$event.stopPropagation()"
|
||||
placeholder=" Filter" ng-model="$parent.query.q" />
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)">
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()){
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue