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 599248b2e56..ab8983faf94 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 @@

Create new interpreter

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 diff --git a/zeppelin-web/src/app/interpreter/interpreter.controller.js b/zeppelin-web/src/app/interpreter/interpreter.controller.js index d1e8889a25b..276657d1276 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(); diff --git a/zeppelin-web/src/app/interpreter/interpreter.css b/zeppelin-web/src/app/interpreter/interpreter.css index 9843636ae76..ee4f81db614 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; diff --git a/zeppelin-web/src/app/interpreter/interpreter.html b/zeppelin-web/src/app/interpreter/interpreter.html index 3c25246067a..b3d3333a0d8 100644 --- a/zeppelin-web/src/app/interpreter/interpreter.html +++ b/zeppelin-web/src/app/interpreter/interpreter.html @@ -84,9 +84,10 @@

Repositories

+ ng-repeat="setting in interpreterSettings | orderBy: 'name' | filter: searchInterpreter" interpreter-directive>
+

{{setting.name}} @@ -191,7 +192,33 @@
Option
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 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 00000000000..1cf1ab2d178 --- /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); + }); + } + } + }; +}); diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html index b23db7a7530..0697dcaa104 100644 --- a/zeppelin-web/src/index.html +++ b/zeppelin-web/src/index.html @@ -164,6 +164,7 @@ + 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 7aac7816c5e..2bcc4c69ac0 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 void setHost(String host) { 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; 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 308cbb05047..079216c76b9 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 Map info() { 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 @@ protected Object jobRun() throws Throwable { 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 @@ protected Object jobRun() throws Throwable { } } + 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());