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));
   }