Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id A5DB7200B67 for ; Tue, 16 Aug 2016 09:02:06 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id A2BD8160AA8; Tue, 16 Aug 2016 07:02:06 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 71DDD160A76 for ; Tue, 16 Aug 2016 09:02:05 +0200 (CEST) Received: (qmail 62421 invoked by uid 500); 16 Aug 2016 07:02:04 -0000 Mailing-List: contact commits-help@zeppelin.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@zeppelin.apache.org Delivered-To: mailing list commits@zeppelin.apache.org Received: (qmail 62412 invoked by uid 99); 16 Aug 2016 07:02:04 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 16 Aug 2016 07:02:04 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 6F0C6E01BD; Tue, 16 Aug 2016 07:02:04 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: bzz@apache.org To: commits@zeppelin.apache.org Message-Id: X-Mailer: ASF-Git Admin Mailer Subject: zeppelin git commit: [Zeppelin-945] Interpreter authorization Date: Tue, 16 Aug 2016 07:02:04 +0000 (UTC) archived-at: Tue, 16 Aug 2016 07:02:06 -0000 Repository: zeppelin Updated Branches: refs/heads/master 6deb79218 -> 43dc32bd3 [Zeppelin-945] Interpreter authorization ### What is this PR for? This PR is derived from https://github.com/apache/zeppelin/pull/1223 ### What type of PR is it? Improvement ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-945 ### How should this be tested? You can change user permission of interpreter at interpreter setting page. ### Screenshots (if appropriate) ![result](https://cloud.githubusercontent.com/assets/3348133/17457867/904e2754-5c3e-11e6-948c-b2969c4b89c7.gif) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: astroshim Closes #1257 from astroshim/ZEPPELIN-945_1 and squashes the following commits: 83097ab [astroshim] fix naming. 2c48ded [astroshim] code refactor. 7ed8ad6 [astroshim] fix js code style 3e25159 [astroshim] move directive to componets f07542a [astroshim] remove comment febce0c [astroshim] fix js e42cb9e [astroshim] fix initialize user value e1e7a35 [astroshim] add interpreter create. e72c097 [astroshim] Merge branch 'master' into ZEPPELIN-945_1 e1679b3 [astroshim] wip e56e5b1 [astroshim] wip 635d523 [astroshim] Merge branch 'master' of https://github.com/apache/zeppelin into ZEPPELIN-945_1 1ae1c6a [astroshim] fix for @AhyoungRyu pointed. b34fdf7 [astroshim] fix for checking intpname 29d8f43 [astroshim] fix js 407d260 [astroshim] fit UX Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/43dc32bd Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/43dc32bd Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/43dc32bd Branch: refs/heads/master Commit: 43dc32bd396df49d682bb768e972ef6c11f950da Parents: 6deb792 Author: astroshim Authored: Tue Aug 16 14:39:51 2016 +0900 Committer: Alexander Bezzubov Committed: Tue Aug 16 16:01:55 2016 +0900 ---------------------------------------------------------------------- .../interpreter-create/interpreter-create.html | 29 ++++++++ .../app/interpreter/interpreter.controller.js | 76 +++++++++++++++++++- .../src/app/interpreter/interpreter.css | 8 +++ .../src/app/interpreter/interpreter.html | 29 +++++++- .../interpreter/interpreter.directive.js | 28 ++++++++ zeppelin-web/src/index.html | 1 + .../zeppelin/interpreter/InterpreterOption.java | 16 ++++- .../org/apache/zeppelin/notebook/Paragraph.java | 52 ++++++++++++++ 8 files changed, 235 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43dc32bd/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html b/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html index ea5d98f..1550126 100644 --- a/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html +++ b/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html @@ -88,6 +88,35 @@ limitations under the License. pu-elastic-input-minwidth="180px" ng-model="newInterpreterSetting.option.port" /> +
+
+ + + +
+
+ +
+ +
+
+

+ Enter comma separated users in the fields.
+ Empty field (*) implies anyone can run this interpreter. +

+
+ + Owners + +
+
+
+
+ + Properties http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43dc32bd/zeppelin-web/src/app/interpreter/interpreter.controller.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/interpreter/interpreter.controller.js b/zeppelin-web/src/app/interpreter/interpreter.controller.js index a04b376..4be35ef 100644 --- a/zeppelin-web/src/app/interpreter/interpreter.controller.js +++ b/zeppelin-web/src/app/interpreter/interpreter.controller.js @@ -22,8 +22,69 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', $scope.showRepositoryInfo = false; $scope._ = _; + $scope.openPermissions = function() { + $scope.showInterpreterAuth = true; + }; + + $scope.closePermissions = function() { + $scope.showInterpreterAuth = false; + }; + + var getSelectJson = function() { + var selectJson = { + tags: false, + multiple: true, + tokenSeparators: [',', ' '], + minimumInputLength: 2, + ajax: { + url: function(params) { + if (!params.term) { + return false; + } + return baseUrlSrv.getRestApiBase() + '/security/userlist/' + params.term; + }, + delay: 250, + processResults: function(data, params) { + var users = []; + if (data.body.users.length !== 0) { + for (var i = 0; i < data.body.users.length; i++) { + users.push({ + 'id': data.body.users[i], + 'text': data.body.users[i] + }); + } + } + return { + results: users, + pagination: { + more: false + } + }; + }, + cache: false + } + }; + return selectJson; + }; + + $scope.togglePermissions = function(intpName) { + angular.element('#' + intpName + 'Users').select2(getSelectJson()); + if ($scope.showInterpreterAuth) { + $scope.closePermissions(); + } else { + $scope.openPermissions(); + } + }; + + $scope.$on('ngRenderFinished', function(event, data) { + for (var setting = 0; setting < $scope.interpreterSettings.length; setting++) { + angular.element('#' + $scope.interpreterSettings[setting].name + 'Users').select2(getSelectJson()); + } + }); + var getInterpreterSettings = function() { - $http.get(baseUrlSrv.getRestApiBase() + '/interpreter/setting').success(function(data, status, headers, config) { + $http.get(baseUrlSrv.getRestApiBase() + '/interpreter/setting') + .success(function(data, status, headers, config) { $scope.interpreterSettings = data.body; checkDownloadingDependencies(); }).error(function(data, status, headers, config) { @@ -114,7 +175,6 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', var setting = $scope.interpreterSettings[index]; option = setting.option; } - if (option.perNoteSession) { return 'scoped'; } else if (option.perNoteProcess) { @@ -148,10 +208,15 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', if (setting.option.isExistingProcess === undefined) { setting.option.isExistingProcess = false; } + if (setting.option.setPermission === undefined) { + setting.option.setPermission = false; + } if (setting.option.remote === undefined) { // remote always true for now setting.option.remote = true; } + setting.option.users = angular.element('#' + setting.name + 'Users').val(); + var request = { option: angular.copy(setting.option), properties: angular.copy(setting.properties), @@ -168,6 +233,7 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', removeTMPSettings(index); thisConfirm.close(); checkDownloadingDependencies(); + $route.reload(); }) .error(function(data, status, headers, config) { console.log('Error %o %o', status, data.message); @@ -275,6 +341,10 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', if (newSetting.depArtifact !== '' || newSetting.depArtifact) { $scope.addNewInterpreterDependency(); } + if (newSetting.option.setPermission === undefined) { + newSetting.option.setPermission = false; + } + newSetting.option.users = angular.element('#newInterpreterUsers').val(); var request = angular.copy($scope.newInterpreterSetting); @@ -311,6 +381,7 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', option: { remote: true, isExistingProcess: false, + setPermission: false, perNoteSession: false, perNoteProcess: false @@ -487,6 +558,7 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', var init = function() { $scope.resetNewInterpreterSetting(); $scope.resetNewRepositorySetting(); + getInterpreterSettings(); getAvailableInterpreters(); getRepositories(); http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43dc32bd/zeppelin-web/src/app/interpreter/interpreter.css ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/interpreter/interpreter.css b/zeppelin-web/src/app/interpreter/interpreter.css index 9843636..ee4f81d 100644 --- a/zeppelin-web/src/app/interpreter/interpreter.css +++ b/zeppelin-web/src/app/interpreter/interpreter.css @@ -74,6 +74,14 @@ overflow-y: auto; } +.permissionsForm { + list-style-type: none; + background: #EFEFEF; + padding: 10px 10px 10px 10px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); + border: 1px solid #E5E5E5; +} + .interpreterSettingAdd { margin : 5px 5px 5px 5px; padding : 10px 10px 10px 10px; http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43dc32bd/zeppelin-web/src/app/interpreter/interpreter.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/interpreter/interpreter.html b/zeppelin-web/src/app/interpreter/interpreter.html index fd88204..7ac1872 100644 --- a/zeppelin-web/src/app/interpreter/interpreter.html +++ b/zeppelin-web/src/app/interpreter/interpreter.html @@ -84,9 +84,10 @@ limitations under the License.
+ ng-repeat="setting in interpreterSettings | orderBy: 'name' | filter: searchInterpreter" interpreter-directive>
+

{{setting.name}} @@ -191,7 +192,33 @@ limitations under the License. pu-elastic-input-minwidth="180px" ng-model="setting.option.port" ng-disabled="!valueform.$visible" />

+
+
+ + + +
+
+ +
+ +
+
+

+ Enter comma separated users in the fields.
+ Empty field (*) implies anyone can run this interpreter. +

+
+ Owners + +
+
+
+
Currently there are no properties and dependencies set for this interpreter http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43dc32bd/zeppelin-web/src/components/interpreter/interpreter.directive.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/components/interpreter/interpreter.directive.js b/zeppelin-web/src/components/interpreter/interpreter.directive.js new file mode 100644 index 0000000..1cf1ab2 --- /dev/null +++ b/zeppelin-web/src/components/interpreter/interpreter.directive.js @@ -0,0 +1,28 @@ +/* + * 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. + */ +'use strict'; + +angular.module('zeppelinWebApp').directive('interpreterDirective', function($timeout) { + return { + restrict: 'A', + link: function(scope, element, attr) { + if (scope.$last === true) { + $timeout(function() { + var id = 'ngRenderFinished'; + scope.$emit(id); + }); + } + } + }; +}); http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43dc32bd/zeppelin-web/src/index.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html index b23db7a..0697dca 100644 --- a/zeppelin-web/src/index.html +++ b/zeppelin-web/src/index.html @@ -164,6 +164,7 @@ limitations under the License. + http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43dc32bd/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterOption.java ---------------------------------------------------------------------- diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterOption.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterOption.java index 7aac781..2bcc4c6 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterOption.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterOption.java @@ -17,6 +17,8 @@ package org.apache.zeppelin.interpreter; +import java.util.List; + /** * */ @@ -28,7 +30,8 @@ public class InterpreterOption { boolean perNoteProcess; boolean isExistingProcess; - + boolean setPermission; + List users; public boolean isExistingProcess() { return isExistingProcess; @@ -46,6 +49,17 @@ public class InterpreterOption { this.host = host; } + public boolean permissionIsSet() { + return setPermission; + } + + public void setUserPermission(boolean setPermission) { + this.setPermission = setPermission; + } + + public List getUsers() { + return users; + } public InterpreterOption() { remote = false; http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43dc32bd/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java ---------------------------------------------------------------------- diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java index 308cbb0..079216c 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java @@ -275,6 +275,19 @@ public class Paragraph extends Job implements Serializable, Cloneable { return null; } + private boolean hasPermission(String user, List intpUsers) { + if (1 > intpUsers.size()) { + return true; + } + + for (String u: intpUsers) { + if (user.trim().equals(u.trim())) { + return true; + } + } + return false; + } + @Override protected Object jobRun() throws Throwable { String replName = getRequiredReplName(); @@ -285,6 +298,17 @@ public class Paragraph extends Job implements Serializable, Cloneable { throw new RuntimeException("Can not find interpreter for " + getRequiredReplName()); } + if (this.noteHasUser() && this.noteHasInterpreters()) { + InterpreterSetting intp = getInterpreterSettingById(repl.getInterpreterGroup().getId()); + if (intp != null && + interpreterHasUser(intp) && + isUserAuthorizedToAccessInterpreter(intp.getOption()) == false) { + logger.error("{} has no permission for {} ", authenticationInfo.getUser(), repl); + return new InterpreterResult(Code.ERROR, authenticationInfo.getUser() + + " has no permission for " + getRequiredReplName()); + } + } + String script = getScriptBody(); // inject form if (repl.getFormType() == FormType.NATIVE) { @@ -339,6 +363,34 @@ public class Paragraph extends Job implements Serializable, Cloneable { } } + private boolean noteHasUser() { + return this.user != null; + } + + private boolean noteHasInterpreters() { + return !factory.getInterpreterSettings(note.getId()).isEmpty(); + } + + private boolean interpreterHasUser(InterpreterSetting intp) { + return intp.getOption().permissionIsSet() && intp.getOption().getUsers() != null; + } + + private boolean isUserAuthorizedToAccessInterpreter(InterpreterOption intpOpt){ + return intpOpt.permissionIsSet() && + hasPermission(authenticationInfo.getUser(), intpOpt.getUsers()); + } + + private InterpreterSetting getInterpreterSettingById(String id) { + InterpreterSetting setting = null; + for (InterpreterSetting i: factory.getInterpreterSettings(note.getId())) { + if (id.startsWith(i.getId())) { + setting = i; + break; + } + } + return setting; + } + @Override protected boolean jobAbort() { Interpreter repl = getRepl(getRequiredReplName());