You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by fe...@apache.org on 2017/08/29 17:06:07 UTC
zeppelin git commit: [ZEPPELIN-2848] Added new type of user to only
run notebook
Repository: zeppelin
Updated Branches:
refs/heads/master 2a3791020 -> e47b30a88
[ZEPPELIN-2848] Added new type of user to only run notebook
### What is this PR for?
The idea of this PR is to provide a new kind of user : Runner.
Basically, what it does is that it just removes write authorization and allow user to read and run note.
### What type of PR is it?
[Feature]
### Todos
* [ ] - Task
### What is the Jira issue?
[ZEPPELIN-2848] https://issues.apache.org/jira/browse/ZEPPELIN-2848
### How should this be tested?
- Log in as admin
- Create new notebook and create a paragraph with the interpreter you want
- Assign runner right to user1
- Log in as user1
- Try to run the paragraph (should work)
- Try to modify the paragraph (should fail)
- Log in as user2
- Try to run the paragraph (should fail)
### Screenshots (if appropriate)
### Questions:
* Does the licenses files need update? No
* Is there breaking changes for older versions? Yes
* Does this needs documentation? No
Author: Paolo Genissel <pa...@1000mercis.com>
Author: gfalcone <pa...@gmail.com>
Author: Paolo Genissel <pa...@gmail.com>
Closes #2526 from gfalcone/new_type_runner and squashes the following commits:
96bba66 [gfalcone] Fix typo on notebook_authorization.md
8ab4512 [gfalcone] Update notebook_authorization.md
22a1eb3 [Paolo Genissel] Fixed typo
d621792 [Paolo Genissel] Fix NotebookSecurityRestApiTest
a67af0f [Paolo Genissel] Fix test
5c43ca9 [Paolo Genissel] Added new type of user
Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/e47b30a8
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/e47b30a8
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/e47b30a8
Branch: refs/heads/master
Commit: e47b30a88f09434cd266946d3858cf34275be6a6
Parents: 2a37910
Author: Paolo Genissel <pa...@1000mercis.com>
Authored: Sun Aug 20 09:28:46 2017 +0200
Committer: Felix Cheung <fe...@apache.org>
Committed: Tue Aug 29 10:05:55 2017 -0700
----------------------------------------------------------------------
docs/setup/security/notebook_authorization.md | 10 +++-
docs/usage/rest_api/notebook.md | 6 ++
.../apache/zeppelin/rest/NotebookRestApi.java | 58 ++++++++++++++----
.../apache/zeppelin/socket/NotebookServer.java | 27 +++++++--
.../zeppelin/integration/AuthenticationIT.java | 2 +
.../rest/NotebookSecurityRestApiTest.java | 11 ++--
.../src/app/notebook/notebook.controller.js | 6 +-
zeppelin-web/src/app/notebook/notebook.css | 5 ++
zeppelin-web/src/app/notebook/notebook.html | 10 +++-
.../notebook/NotebookAuthorization.java | 62 +++++++++++++++++++-
.../notebook/repo/NotebookRepoSync.java | 4 ++
.../apache/zeppelin/notebook/NotebookTest.java | 33 +++++++++--
.../notebook/repo/NotebookRepoSyncTest.java | 5 ++
13 files changed, 203 insertions(+), 36 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e47b30a8/docs/setup/security/notebook_authorization.md
----------------------------------------------------------------------
diff --git a/docs/setup/security/notebook_authorization.md b/docs/setup/security/notebook_authorization.md
index 0d55104..fe0e27a 100644
--- a/docs/setup/security/notebook_authorization.md
+++ b/docs/setup/security/notebook_authorization.md
@@ -36,13 +36,17 @@ As you can see, each Zeppelin notebooks has 3 entities :
* Owners ( users or groups )
* Readers ( users or groups )
* Writers ( users or groups )
+* Runners ( users or groups )
<center><img src="{{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/permission_setting.png"></center>
Fill out the each forms with comma seperated **users** and **groups** configured in `conf/shiro.ini` file.
If the form is empty (*), it means that any users can perform that operation.
-If someone who doesn't have **read** permission is trying to access the notebook or someone who doesn't have **write** permission is trying to edit the notebook, Zeppelin will ask to login or block the user.
+If someone who doesn't have **read** permission is trying to access the notebook or someone who doesn't have **write** permission is trying to edit the notebook,
+or someone who doesn't have **run** permission is trying to run a paragraph Zeppelin will ask to login or block the user.
+
+By default, owners and writers have **write** permission, owners, writers and runners have **run** permission, owners, writers, runners and readers have **read** permission
<center><img src="{{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/insufficient_privileges.png"></center>
@@ -63,13 +67,13 @@ or set `zeppelin.notebook.public` property to `false` in `conf/zeppelin-site.xml
</property>
```
-Behind the scenes, when you create a new note only the `owners` field is filled with current user, leaving `readers` and `writers` fields empty. All the notes with at least one empty authorization field are considered to be in `public` workspace. Thus when setting `zeppelin.notebook.public` (or corresponding `ZEPPELIN_NOTEBOOK_PUBLIC`) to false, newly created notes have `readers` and `writers` fields filled with current user, making note appear as in `private` workspace.
+Behind the scenes, when you create a new note only the `owners` field is filled with current user, leaving `readers`, `runners` and `writers` fields empty. All the notes with at least one empty authorization field are considered to be in `public` workspace. Thus when setting `zeppelin.notebook.public` (or corresponding `ZEPPELIN_NOTEBOOK_PUBLIC`) to false, newly created notes have `readers`, `runners`, `writers` fields filled with current user, making note appear as in `private` workspace.
## How it works
In this section, we will explain the detail about how the notebook authorization works in backend side.
### NotebookServer
-The [NotebookServer](https://github.com/apache/zeppelin/blob/master/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java) classifies every notebook operations into three categories: **Read**, **Write**, **Manage**.
+The [NotebookServer](https://github.com/apache/zeppelin/blob/master/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java) classifies every notebook operations into three categories: **Read**, **Run**, **Write**, **Manage**.
Before executing a notebook operation, it checks if the user and the groups associated with the `NotebookSocket` have permissions.
For example, before executing a **Read** operation, it checks if the user and the groups have at least one entity that belongs to the **Reader** entities.
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e47b30a8/docs/usage/rest_api/notebook.md
----------------------------------------------------------------------
diff --git a/docs/usage/rest_api/notebook.md b/docs/usage/rest_api/notebook.md
index dfb491a..ff93553 100644
--- a/docs/usage/rest_api/notebook.md
+++ b/docs/usage/rest_api/notebook.md
@@ -1215,6 +1215,9 @@ Notebooks REST API supports the following operations: List, Create, Get, Delete,
"owners":[
"user1"
],
+ "runners":[
+ "user2"
+ ],
"writers":[
"user2"
]
@@ -1259,6 +1262,9 @@ Notebooks REST API supports the following operations: List, Create, Get, Delete,
"owners": [
"user2"
],
+ "runners":[
+ "user2"
+ ],
"writers": [
"user1"
]
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e47b30a8/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
index a343879..c170a09 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
@@ -98,6 +98,7 @@ public class NotebookRestApi {
permissionsMap.put("owners", notebookAuthorization.getOwners(noteId));
permissionsMap.put("readers", notebookAuthorization.getReaders(noteId));
permissionsMap.put("writers", notebookAuthorization.getWriters(noteId));
+ permissionsMap.put("runners", notebookAuthorization.getRunners(noteId));
return new JsonResponse<>(Status.OK, "", permissionsMap).build();
}
@@ -165,6 +166,18 @@ public class NotebookRestApi {
throw new ForbiddenException(errorMsg);
}
}
+
+ /**
+ * Check if the current user can run the given note.
+ */
+ private void checkIfUserCanRun(String noteId, String errorMsg) {
+ Set<String> userAndRoles = Sets.newHashSet();
+ userAndRoles.add(SecurityUtils.getPrincipal());
+ userAndRoles.addAll(SecurityUtils.getRoles());
+ if (!notebookAuthorization.hasRunAuthorization(userAndRoles, noteId)) {
+ throw new ForbiddenException(errorMsg);
+ }
+ }
private void checkIfNoteIsNotNull(Note note) {
if (note == null) {
@@ -199,15 +212,31 @@ public class NotebookRestApi {
HashMap<String, HashSet<String>> permMap =
gson.fromJson(req, new TypeToken<HashMap<String, HashSet<String>>>() {}.getType());
Note note = notebook.getNote(noteId);
-
- LOG.info("Set permissions {} {} {} {} {}", noteId, principal, permMap.get("owners"),
- permMap.get("readers"), permMap.get("writers"));
+
+ LOG.info("Set permissions {} {} {} {} {} {}", noteId, principal, permMap.get("owners"),
+ permMap.get("readers"), permMap.get("runners"), permMap.get("writers"));
HashSet<String> readers = permMap.get("readers");
+ HashSet<String> runners = permMap.get("runners");
HashSet<String> owners = permMap.get("owners");
HashSet<String> writers = permMap.get("writers");
- // Set readers, if writers and owners is empty -> set to user requesting the change
+ // Set readers, if runners, writers and owners is empty -> set to user requesting the change
if (readers != null && !readers.isEmpty()) {
+ if (runners.isEmpty()) {
+ runners = Sets.newHashSet(SecurityUtils.getPrincipal());
+ }
+ if (writers.isEmpty()) {
+ writers = Sets.newHashSet(SecurityUtils.getPrincipal());
+ }
+ if (owners.isEmpty()) {
+ owners = Sets.newHashSet(SecurityUtils.getPrincipal());
+ }
+ }
+ // Set runners, if writers and owners is empty -> set to user requesting the change
+ if (runners != null && !runners.isEmpty()) {
+ if (writers.isEmpty()) {
+ writers = Sets.newHashSet(SecurityUtils.getPrincipal());
+ }
if (owners.isEmpty()) {
owners = Sets.newHashSet(SecurityUtils.getPrincipal());
}
@@ -220,10 +249,12 @@ public class NotebookRestApi {
}
notebookAuthorization.setReaders(noteId, readers);
+ notebookAuthorization.setRunners(noteId, runners);
notebookAuthorization.setWriters(noteId, writers);
notebookAuthorization.setOwners(noteId, owners);
- LOG.debug("After set permissions {} {} {}", notebookAuthorization.getOwners(noteId),
- notebookAuthorization.getReaders(noteId), notebookAuthorization.getWriters(noteId));
+ LOG.debug("After set permissions {} {} {} {}", notebookAuthorization.getOwners(noteId),
+ notebookAuthorization.getReaders(noteId), notebookAuthorization.getRunners(noteId),
+ notebookAuthorization.getWriters(noteId));
AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
note.persist(subject);
notebookServer.broadcastNote(note);
@@ -589,7 +620,7 @@ public class NotebookRestApi {
Note note = notebook.getNote(noteId);
AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
checkIfNoteIsNotNull(note);
- checkIfUserCanWrite(noteId, "Insufficient privileges you cannot run job for this note");
+ checkIfUserCanRun(noteId, "Insufficient privileges you cannot run job for this note");
try {
note.runAll(subject);
@@ -616,7 +647,7 @@ public class NotebookRestApi {
LOG.info("stop note jobs {} ", noteId);
Note note = notebook.getNote(noteId);
checkIfNoteIsNotNull(note);
- checkIfUserCanWrite(noteId, "Insufficient privileges you cannot stop this job for this note");
+ checkIfUserCanRun(noteId, "Insufficient privileges you cannot stop this job for this note");
for (Paragraph p : note.getParagraphs()) {
if (!p.isTerminated()) {
@@ -690,7 +721,7 @@ public class NotebookRestApi {
Note note = notebook.getNote(noteId);
checkIfNoteIsNotNull(note);
- checkIfUserCanWrite(noteId, "Insufficient privileges you cannot run job for this note");
+ checkIfUserCanRun(noteId, "Insufficient privileges you cannot run job for this note");
Paragraph paragraph = note.getParagraph(paragraphId);
checkIfParagraphIsNotNull(paragraph);
@@ -728,7 +759,7 @@ public class NotebookRestApi {
Note note = notebook.getNote(noteId);
checkIfNoteIsNotNull(note);
- checkIfUserCanWrite(noteId, "Insufficient privileges you cannot run paragraph");
+ checkIfUserCanRun(noteId, "Insufficient privileges you cannot run paragraph");
Paragraph paragraph = note.getParagraph(paragraphId);
checkIfParagraphIsNotNull(paragraph);
@@ -766,7 +797,7 @@ public class NotebookRestApi {
LOG.info("stop paragraph job {} ", noteId);
Note note = notebook.getNote(noteId);
checkIfNoteIsNotNull(note);
- checkIfUserCanWrite(noteId, "Insufficient privileges you cannot stop paragraph");
+ checkIfUserCanRun(noteId, "Insufficient privileges you cannot stop paragraph");
Paragraph p = note.getParagraph(paragraphId);
checkIfParagraphIsNotNull(p);
p.abort();
@@ -791,7 +822,7 @@ public class NotebookRestApi {
Note note = notebook.getNote(noteId);
checkIfNoteIsNotNull(note);
- checkIfUserCanWrite(noteId, "Insufficient privileges you cannot set a cron job for this note");
+ checkIfUserCanRun(noteId, "Insufficient privileges you cannot set a cron job for this note");
if (!CronExpression.isValidExpression(request.getCronString())) {
return new JsonResponse<>(Status.BAD_REQUEST, "wrong cron expressions.").build();
@@ -922,7 +953,8 @@ public class NotebookRestApi {
String noteId = Id[0];
if (!notebookAuthorization.isOwner(noteId, userAndRoles) &&
!notebookAuthorization.isReader(noteId, userAndRoles) &&
- !notebookAuthorization.isWriter(noteId, userAndRoles)) {
+ !notebookAuthorization.isWriter(noteId, userAndRoles) &&
+ !notebookAuthorization.isRunner(noteId, userAndRoles)) {
notesFound.remove(i);
i--;
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e47b30a8/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
index 3ddeec0..f0e0bb2 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
@@ -752,6 +752,25 @@ public class NotebookServer extends WebSocketServlet
}
/**
+ * @return false if user doesn't have runner permission for this paragraph
+ */
+ private boolean hasParagraphRunnerPermission(NotebookSocket conn,
+ Notebook notebook, String noteId,
+ HashSet<String> userAndRoles,
+ String principal, String op)
+ throws IOException {
+
+ NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
+ if (!notebookAuthorization.isRunner(noteId, userAndRoles)) {
+ permissionError(conn, op, principal, userAndRoles,
+ notebookAuthorization.getOwners(noteId));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* @return false if user doesn't have writer permission for this paragraph
*/
private boolean hasParagraphWriterPermission(NotebookSocket conn,
@@ -1621,7 +1640,7 @@ public class NotebookServer extends WebSocketServlet
String noteId = getOpenNoteId(conn);
- if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ if (!hasParagraphRunnerPermission(conn, notebook, noteId,
userAndRoles, fromMessage.principal, "write")) {
return;
}
@@ -1639,7 +1658,7 @@ public class NotebookServer extends WebSocketServlet
return;
}
- if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ if (!hasParagraphRunnerPermission(conn, notebook, noteId,
userAndRoles, fromMessage.principal, "run all paragraphs")) {
return;
}
@@ -1732,7 +1751,7 @@ public class NotebookServer extends WebSocketServlet
String noteId = getOpenNoteId(conn);
- if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ if (!hasParagraphRunnerPermission(conn, notebook, noteId,
userAndRoles, fromMessage.principal, "write")) {
return;
}
@@ -2060,7 +2079,7 @@ public class NotebookServer extends WebSocketServlet
Set<String> userAndRoles = Sets.newHashSet();
userAndRoles.add(SecurityUtils.getPrincipal());
userAndRoles.addAll(SecurityUtils.getRoles());
- if (!notebookIns.getNotebookAuthorization().hasWriteAuthorization(userAndRoles, noteId)) {
+ if (!notebookIns.getNotebookAuthorization().hasRunAuthorization(userAndRoles, noteId)) {
throw new ForbiddenException(String.format("can't execute note %s", noteId));
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e47b30a8/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java
index 38fe574..7debf1b 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java
@@ -258,6 +258,8 @@ public class AuthenticationIT extends AbstractZeppelinIT {
MAX_BROWSER_TIMEOUT_SEC).sendKeys("finance ");
pollingWait(By.xpath(".//*[@id='selectReaders']/following::span//input"),
MAX_BROWSER_TIMEOUT_SEC).sendKeys("finance ");
+ pollingWait(By.xpath(".//*[@id='selectRunners']/following::span//input"),
+ MAX_BROWSER_TIMEOUT_SEC).sendKeys("finance ");
pollingWait(By.xpath(".//*[@id='selectWriters']/following::span//input"),
MAX_BROWSER_TIMEOUT_SEC).sendKeys("finance ");
pollingWait(By.xpath("//button[@ng-click='savePermissions()']"), MAX_BROWSER_TIMEOUT_SEC)
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e47b30a8/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java
index 367a199..c3b0977 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java
@@ -81,7 +81,7 @@ public class NotebookSecurityRestApiTest extends AbstractTestRestApi {
String noteId = createNoteForUser("test", "admin", "password1");
//set permission
- String payload = "{ \"owners\": [\"admin\"], \"readers\": [\"user2\"], \"writers\": [\"user2\"] }";
+ String payload = "{ \"owners\": [\"admin\"], \"readers\": [\"user2\"], \"runners\": [\"user2\"], \"writers\": [\"user2\"] }";
PutMethod put = httpPut("/notebook/" + noteId + "/permissions", payload , "admin", "password1");
assertThat("test set note permission method:", put, isAllowed());
put.releaseConnection();
@@ -98,7 +98,7 @@ public class NotebookSecurityRestApiTest extends AbstractTestRestApi {
String noteId = createNoteForUser("test", "admin", "password1");
//set permission
- String payload = "{ \"owners\": [\"admin\", \"user1\"], \"readers\": [\"user2\"], \"writers\": [\"user2\"] }";
+ String payload = "{ \"owners\": [\"admin\", \"user1\"], \"readers\": [\"user2\"], \"runners\": [\"user2\"], \"writers\": [\"user2\"] }";
PutMethod put = httpPut("/notebook/" + noteId + "/permissions", payload , "admin", "password1");
assertThat("test set note permission method:", put, isAllowed());
put.releaseConnection();
@@ -180,7 +180,7 @@ public class NotebookSecurityRestApiTest extends AbstractTestRestApi {
}
private void setPermissionForNote(String noteId, String user, String pwd) throws IOException {
- String payload = "{\"owners\":[\"" + user + "\"],\"readers\":[\"" + user + "\"],\"writers\":[\"" + user + "\"]}";
+ String payload = "{\"owners\":[\"" + user + "\"],\"readers\":[\"" + user + "\"],\"runners\":[\"" + user + "\"],\"writers\":[\"" + user + "\"]}";
PutMethod put = httpPut(("/notebook/" + noteId + "/permissions"), payload, user, pwd);
put.releaseConnection();
}
@@ -206,10 +206,11 @@ public class NotebookSecurityRestApiTest extends AbstractTestRestApi {
ArrayList owners = permissions.get("owners");
ArrayList readers = permissions.get("readers");
ArrayList writers = permissions.get("writers");
+ ArrayList runners = permissions.get("runners");
- if (owners.size() != 0 && readers.size() != 0 && writers.size() != 0) {
+ if (owners.size() != 0 && readers.size() != 0 && writers.size() != 0 && runners.size() != 0) {
assertEquals("User has permissions ", true, (owners.contains(user) || readers.contains(user) ||
- writers.contains(user)));
+ writers.contains(user) || runners.contains(user)));
}
getPermission.releaseConnection();
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e47b30a8/zeppelin-web/src/app/notebook/notebook.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js
index 4b8b23f..bdd47c2 100644
--- a/zeppelin-web/src/app/notebook/notebook.controller.js
+++ b/zeppelin-web/src/app/notebook/notebook.controller.js
@@ -696,6 +696,7 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
$scope.setIamOwner()
angular.element('#selectOwners').select2(selectJson)
angular.element('#selectReaders').select2(selectJson)
+ angular.element('#selectRunners').select2(selectJson)
angular.element('#selectWriters').select2(selectJson)
if (callback) {
callback()
@@ -735,6 +736,7 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
function convertPermissionsToArray () {
$scope.permissions.owners = angular.element('#selectOwners').val()
$scope.permissions.readers = angular.element('#selectReaders').val()
+ $scope.permissions.runners = angular.element('#selectRunners').val()
$scope.permissions.writers = angular.element('#selectWriters').val()
angular.element('.permissionsForm select').find('option:not([is-select2="false"])').remove()
}
@@ -1013,7 +1015,8 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
closable: true,
title: 'Permissions Saved Successfully',
message: 'Owners : ' + $scope.permissions.owners + '\n\n' + 'Readers : ' +
- $scope.permissions.readers + '\n\n' + 'Writers : ' + $scope.permissions.writers
+ $scope.permissions.readers + '\n\n' + 'Runners : ' + $scope.permissions.runners +
+ '\n\n' + 'Writers : ' + $scope.permissions.writers
})
$scope.showPermissions = false
})
@@ -1058,6 +1061,7 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
$scope.closePermissions()
angular.element('#selectOwners').select2({})
angular.element('#selectReaders').select2({})
+ angular.element('#selectRunners').select2({})
angular.element('#selectWriters').select2({})
} else {
$scope.openPermissions()
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e47b30a8/zeppelin-web/src/app/notebook/notebook.css
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/notebook.css b/zeppelin-web/src/app/notebook/notebook.css
index a7b7a7f..3a09647 100644
--- a/zeppelin-web/src/app/notebook/notebook.css
+++ b/zeppelin-web/src/app/notebook/notebook.css
@@ -219,6 +219,11 @@
display: inline-block;
}
+.permissions .runners {
+ width:60px;
+ display: inline-block;
+}
+
.permissions .writers {
width:60px;
display: inline-block;
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e47b30a8/zeppelin-web/src/app/notebook/notebook.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/notebook.html b/zeppelin-web/src/app/notebook/notebook.html
index b96d08a..98b66fe 100644
--- a/zeppelin-web/src/app/notebook/notebook.html
+++ b/zeppelin-web/src/app/notebook/notebook.html
@@ -81,13 +81,19 @@ limitations under the License.
<select id="selectOwners" multiple="multiple">
<option is-select2="false" ng-repeat="owner in permissions.owners" selected="selected">{{owner}}</option>
</select>
- Owners can change permissions,read and write the note.
+ Owners can change permissions,read, run and write the note.
</p>
<p><span class="writers">Writers </span>
<select id="selectWriters" multiple="multiple">
<option is-select2="false" ng-repeat="writers in permissions.writers" selected="selected">{{writers}}</option>
</select>
- Writers can read and write the note.
+ Writers can read, run and write the note.
+ </p>
+ <p><span class="runners">Runners </span>
+ <select id="selectRunners" multiple="multiple">
+ <option is-select2="false" ng-repeat="runners in permissions.runners" selected="selected">{{runners}}</option>
+ </select>
+ Runners can read and run the note.
</p>
<p><span class="readers">Readers </span>
<select id="selectReaders" multiple="multiple">
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e47b30a8/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookAuthorization.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookAuthorization.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookAuthorization.java
index 500f068..69ba891 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookAuthorization.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookAuthorization.java
@@ -52,7 +52,8 @@ public class NotebookAuthorization {
private static final Logger LOG = LoggerFactory.getLogger(NotebookAuthorization.class);
private static NotebookAuthorization instance = null;
/*
- * { "note1": { "owners": ["u1"], "readers": ["u1", "u2"], "writers": ["u1"] }, "note2": ... } }
+ * { "note1": { "owners": ["u1"], "readers": ["u1", "u2"], "runners": ["u2"],
+ * "writers": ["u1"] }, "note2": ... } }
*/
private static Map<String, Map<String, Set<String>>> authInfo = new HashMap<>();
/*
@@ -177,6 +178,7 @@ public class NotebookAuthorization {
noteAuthInfo = new LinkedHashMap();
noteAuthInfo.put("owners", new LinkedHashSet(entities));
noteAuthInfo.put("readers", new LinkedHashSet());
+ noteAuthInfo.put("runners", new LinkedHashSet());
noteAuthInfo.put("writers", new LinkedHashSet());
} else {
noteAuthInfo.put("owners", new LinkedHashSet(entities));
@@ -192,6 +194,7 @@ public class NotebookAuthorization {
noteAuthInfo = new LinkedHashMap();
noteAuthInfo.put("owners", new LinkedHashSet());
noteAuthInfo.put("readers", new LinkedHashSet(entities));
+ noteAuthInfo.put("runners", new LinkedHashSet());
noteAuthInfo.put("writers", new LinkedHashSet());
} else {
noteAuthInfo.put("readers", new LinkedHashSet(entities));
@@ -200,6 +203,23 @@ public class NotebookAuthorization {
saveToFile();
}
+ public void setRunners(String noteId, Set<String> entities) {
+ Map<String, Set<String>> noteAuthInfo = authInfo.get(noteId);
+ entities = validateUser(entities);
+ if (noteAuthInfo == null) {
+ noteAuthInfo = new LinkedHashMap();
+ noteAuthInfo.put("owners", new LinkedHashSet());
+ noteAuthInfo.put("readers", new LinkedHashSet());
+ noteAuthInfo.put("runners", new LinkedHashSet(entities));
+ noteAuthInfo.put("writers", new LinkedHashSet());
+ } else {
+ noteAuthInfo.put("runners", new LinkedHashSet(entities));
+ }
+ authInfo.put(noteId, noteAuthInfo);
+ saveToFile();
+ }
+
+
public void setWriters(String noteId, Set<String> entities) {
Map<String, Set<String>> noteAuthInfo = authInfo.get(noteId);
entities = validateUser(entities);
@@ -207,6 +227,7 @@ public class NotebookAuthorization {
noteAuthInfo = new LinkedHashMap();
noteAuthInfo.put("owners", new LinkedHashSet());
noteAuthInfo.put("readers", new LinkedHashSet());
+ noteAuthInfo.put("runners", new LinkedHashSet());
noteAuthInfo.put("writers", new LinkedHashSet(entities));
} else {
noteAuthInfo.put("writers", new LinkedHashSet(entities));
@@ -243,6 +264,20 @@ public class NotebookAuthorization {
return entities;
}
+ public Set<String> getRunners(String noteId) {
+ Map<String, Set<String>> noteAuthInfo = authInfo.get(noteId);
+ Set<String> entities = null;
+ if (noteAuthInfo == null) {
+ entities = new HashSet<>();
+ } else {
+ entities = noteAuthInfo.get("runners");
+ if (entities == null) {
+ entities = new HashSet<>();
+ }
+ }
+ return entities;
+ }
+
public Set<String> getWriters(String noteId) {
Map<String, Set<String>> noteAuthInfo = authInfo.get(noteId);
Set<String> entities = null;
@@ -268,7 +303,14 @@ public class NotebookAuthorization {
public boolean isReader(String noteId, Set<String> entities) {
return isMember(entities, getReaders(noteId)) ||
isMember(entities, getOwners(noteId)) ||
- isMember(entities, getWriters(noteId));
+ isMember(entities, getWriters(noteId)) ||
+ isMember(entities, getRunners(noteId));
+ }
+
+ public boolean isRunner(String noteId, Set<String> entities) {
+ return isMember(entities, getRunners(noteId)) ||
+ isMember(entities, getWriters(noteId)) ||
+ isMember(entities, getOwners(noteId));
}
// return true if b is empty or if (a intersection b) is non-empty
@@ -311,6 +353,17 @@ public class NotebookAuthorization {
return isReader(noteId, userAndRoles);
}
+ public boolean hasRunAuthorization(Set<String> userAndRoles, String noteId) {
+ if (conf.isAnonymousAllowed()) {
+ LOG.debug("Zeppelin runs in anonymous mode, everybody is runner");
+ return true;
+ }
+ if (userAndRoles == null) {
+ return false;
+ }
+ return isRunner(noteId, userAndRoles);
+ }
+
public void removeNote(String noteId) {
authInfo.remove(noteId);
saveToFile();
@@ -337,13 +390,16 @@ public class NotebookAuthorization {
owners.add(subject.getUser());
setOwners(noteId, owners);
} else {
- // add current user to owners, readers, writers - private note
+ // add current user to owners, readers, runners, 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 = getRunners(noteId);
+ entities.add(subject.getUser());
+ setRunners(noteId, entities);
entities = getWriters(noteId);
entities.add(subject.getUser());
setWriters(noteId, entities);
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e47b30a8/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java
index 28de7c8..30af83f 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java
@@ -285,6 +285,7 @@ public class NotebookRepoSync implements NotebookRepo {
NotebookAuthorization notebookAuthorization = NotebookAuthorization.getInstance();
return notebookAuthorization.getOwners(noteId).isEmpty()
&& notebookAuthorization.getReaders(noteId).isEmpty()
+ && notebookAuthorization.getRunners(noteId).isEmpty()
&& notebookAuthorization.getWriters(noteId).isEmpty();
}
@@ -300,6 +301,9 @@ public class NotebookRepoSync implements NotebookRepo {
users = notebookAuthorization.getReaders(noteId);
users.add(subject.getUser());
notebookAuthorization.setReaders(noteId, users);
+ users = notebookAuthorization.getRunners(noteId);
+ users.add(subject.getUser());
+ notebookAuthorization.setRunners(noteId, users);
users = notebookAuthorization.getWriters(noteId);
users.add(subject.getUser());
notebookAuthorization.setWriters(noteId, users);
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e47b30a8/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
index e1a20b5..634ac30 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
@@ -721,6 +721,8 @@ public class NotebookTest implements JobListenerFactory{
new HashSet<>(Arrays.asList("user2"))), true);
assertEquals(notebookAuthorization.isReader(note.getId(),
new HashSet<>(Arrays.asList("user2"))), true);
+ assertEquals(notebookAuthorization.isRunner(note.getId(),
+ new HashSet<>(Arrays.asList("user2"))), true);
assertEquals(notebookAuthorization.isWriter(note.getId(),
new HashSet<>(Arrays.asList("user2"))), true);
@@ -728,6 +730,8 @@ public class NotebookTest implements JobListenerFactory{
new HashSet<>(Arrays.asList("user1")));
notebookAuthorization.setReaders(note.getId(),
new HashSet<>(Arrays.asList("user1", "user2")));
+ notebookAuthorization.setRunners(note.getId(),
+ new HashSet<>(Arrays.asList("user3")));
notebookAuthorization.setWriters(note.getId(),
new HashSet<>(Arrays.asList("user1")));
@@ -737,21 +741,26 @@ public class NotebookTest implements JobListenerFactory{
new HashSet<>(Arrays.asList("user1"))), true);
assertEquals(notebookAuthorization.isReader(note.getId(),
- new HashSet<>(Arrays.asList("user3"))), false);
+ new HashSet<>(Arrays.asList("user4"))), false);
assertEquals(notebookAuthorization.isReader(note.getId(),
new HashSet<>(Arrays.asList("user2"))), true);
+ assertEquals(notebookAuthorization.isRunner(note.getId(),
+ new HashSet<>(Arrays.asList("user3"))), true);
+ assertEquals(notebookAuthorization.isRunner(note.getId(),
+ new HashSet<>(Arrays.asList("user2"))), false);
+
assertEquals(notebookAuthorization.isWriter(note.getId(),
new HashSet<>(Arrays.asList("user2"))), false);
assertEquals(notebookAuthorization.isWriter(note.getId(),
new HashSet<>(Arrays.asList("user1"))), true);
- // Test clearing of permssions
+ // Test clearing of permissions
notebookAuthorization.setReaders(note.getId(), Sets.<String>newHashSet());
assertEquals(notebookAuthorization.isReader(note.getId(),
new HashSet<>(Arrays.asList("user2"))), true);
assertEquals(notebookAuthorization.isReader(note.getId(),
- new HashSet<>(Arrays.asList("user3"))), true);
+ new HashSet<>(Arrays.asList("user4"))), true);
notebook.removeNote(note.getId(), anonymous);
}
@@ -767,11 +776,13 @@ public class NotebookTest implements JobListenerFactory{
Note note = notebook.createNote(new AuthenticationInfo(user1));
- // check that user1 is owner, reader and writer
+ // check that user1 is owner, reader, runner and writer
assertEquals(notebookAuthorization.isOwner(note.getId(),
Sets.newHashSet(user1)), true);
assertEquals(notebookAuthorization.isReader(note.getId(),
Sets.newHashSet(user1)), true);
+ assertEquals(notebookAuthorization.isRunner(note.getId(),
+ Sets.newHashSet(user2)), true);
assertEquals(notebookAuthorization.isWriter(note.getId(),
Sets.newHashSet(user1)), true);
@@ -780,6 +791,8 @@ public class NotebookTest implements JobListenerFactory{
Sets.newHashSet(user2)), false);
assertEquals(notebookAuthorization.isReader(note.getId(),
Sets.newHashSet(user2)), true);
+ assertEquals(notebookAuthorization.isRunner(note.getId(),
+ Sets.newHashSet(user2)), true);
assertEquals(notebookAuthorization.isWriter(note.getId(),
Sets.newHashSet(user2)), true);
@@ -790,7 +803,7 @@ public class NotebookTest implements JobListenerFactory{
assertEquals(user1Notes.size(), 1);
assertEquals(user1Notes.get(0).getId(), note.getId());
- // check that user2 has note listed in his workbech because of admin role
+ // check that user2 has note listed in his workbench because of admin role
Set<String> user2AndRoles = notebookAuthorization.getRoles(user2);
user2AndRoles.add(user2);
List<Note> user2Notes = notebook.getAllNotes(user2AndRoles);
@@ -1094,6 +1107,7 @@ public class NotebookTest implements JobListenerFactory{
notebook.getNotebookAuthorization().setOwners(note1.getId(), Sets.newHashSet("user1"));
notebook.getNotebookAuthorization().setWriters(note1.getId(), Sets.newHashSet("user1"));
+ notebook.getNotebookAuthorization().setRunners(note1.getId(), Sets.newHashSet("user1"));
notebook.getNotebookAuthorization().setReaders(note1.getId(), Sets.newHashSet("user1"));
assertEquals(1, notebook.getAllNotes(Sets.newHashSet("anonymous")).size());
assertEquals(2, notebook.getAllNotes(Sets.newHashSet("user1")).size());
@@ -1101,6 +1115,7 @@ public class NotebookTest implements JobListenerFactory{
notebook.getNotebookAuthorization().setOwners(note2.getId(), Sets.newHashSet("user2"));
notebook.getNotebookAuthorization().setWriters(note2.getId(), Sets.newHashSet("user2"));
notebook.getNotebookAuthorization().setReaders(note2.getId(), Sets.newHashSet("user2"));
+ notebook.getNotebookAuthorization().setRunners(note2.getId(), Sets.newHashSet("user2"));
assertEquals(0, notebook.getAllNotes(Sets.newHashSet("anonymous")).size());
assertEquals(1, notebook.getAllNotes(Sets.newHashSet("user1")).size());
assertEquals(1, notebook.getAllNotes(Sets.newHashSet("user2")).size());
@@ -1133,6 +1148,12 @@ public class NotebookTest implements JobListenerFactory{
notes2 = notebook.getAllNotes(user2);
assertEquals(notes1.size(), 1);
assertEquals(notes2.size(), 1);
+
+ notebook.getNotebookAuthorization().setRunners(note.getId(), Sets.newHashSet("user1"));
+ notes1 = notebook.getAllNotes(user1);
+ notes2 = notebook.getAllNotes(user2);
+ assertEquals(notes1.size(), 1);
+ assertEquals(notes2.size(), 1);
notebook.getNotebookAuthorization().setWriters(note.getId(), Sets.newHashSet("user1"));
notes1 = notebook.getAllNotes(user1);
@@ -1169,6 +1190,7 @@ public class NotebookTest implements JobListenerFactory{
// user1 is only owner
assertEquals(notebookAuthorization.getOwners(notePublic.getId()).size(), 1);
assertEquals(notebookAuthorization.getReaders(notePublic.getId()).size(), 0);
+ assertEquals(notebookAuthorization.getRunners(notePublic.getId()).size(), 0);
assertEquals(notebookAuthorization.getWriters(notePublic.getId()).size(), 0);
// case of private note
@@ -1197,6 +1219,7 @@ public class NotebookTest implements JobListenerFactory{
// user1 have all rights
assertEquals(notebookAuthorization.getOwners(notePrivate.getId()).size(), 1);
assertEquals(notebookAuthorization.getReaders(notePrivate.getId()).size(), 1);
+ assertEquals(notebookAuthorization.getRunners(notePrivate.getId()).size(), 1);
assertEquals(notebookAuthorization.getWriters(notePrivate.getId()).size(), 1);
//set back public to true
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e47b30a8/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
index 803912e..a6c9393 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
@@ -330,6 +330,7 @@ public class NotebookRepoSyncTest implements JobListenerFactory {
assertEquals(true, authInfo.isOwner(note.getId(), entity));
assertEquals(1, authInfo.getOwners(note.getId()).size());
assertEquals(0, authInfo.getReaders(note.getId()).size());
+ assertEquals(0, authInfo.getRunners(note.getId()).size());
assertEquals(0, authInfo.getWriters(note.getId()).size());
/* update note and save on secondary storage */
@@ -354,6 +355,7 @@ public class NotebookRepoSyncTest implements JobListenerFactory {
assertEquals(true, authInfo.isOwner(note.getId(), entity));
assertEquals(1, authInfo.getOwners(note.getId()).size());
assertEquals(0, authInfo.getReaders(note.getId()).size());
+ assertEquals(0, authInfo.getRunners(note.getId()).size());
assertEquals(0, authInfo.getWriters(note.getId()).size());
/* scenario 2 - note doesn't exist on main storage */
@@ -364,6 +366,7 @@ public class NotebookRepoSyncTest implements JobListenerFactory {
authInfo.removeNote(note.getId());
assertEquals(0, authInfo.getOwners(note.getId()).size());
assertEquals(0, authInfo.getReaders(note.getId()).size());
+ assertEquals(0, authInfo.getRunners(note.getId()).size());
assertEquals(0, authInfo.getWriters(note.getId()).size());
/* now sync - should bring note from secondary storage with added acl */
@@ -372,9 +375,11 @@ public class NotebookRepoSyncTest implements JobListenerFactory {
assertEquals(1, notebookRepoSync.list(1, null).size());
assertEquals(1, authInfo.getOwners(note.getId()).size());
assertEquals(1, authInfo.getReaders(note.getId()).size());
+ assertEquals(1, authInfo.getRunners(note.getId()).size());
assertEquals(1, authInfo.getWriters(note.getId()).size());
assertEquals(true, authInfo.isOwner(note.getId(), entity));
assertEquals(true, authInfo.isReader(note.getId(), entity));
+ assertEquals(true, authInfo.isRunner(note.getId(), entity));
assertEquals(true, authInfo.isWriter(note.getId(), entity));
}