From 0793c109ff978b78bf50be055f0fcc6b4dc4e309 Mon Sep 17 00:00:00 2001
From: Prabhjyot Singh
Date: Wed, 27 Jul 2016 19:10:24 +0530
Subject: [PATCH 1/2] Auto-suggestion of notebook permissions should list group
as well
---
.../org/apache/zeppelin/rest/GetUserList.java | 19 ++
.../apache/zeppelin/rest/SecurityRestApi.java | 24 +-
.../integration/AuthenticationIT.java | 12 +-
zeppelin-web/bower.json | 3 +-
zeppelin-web/src/app/app.js | 7 +
.../src/app/notebook/notebook.controller.js | 266 ++++--------------
zeppelin-web/src/app/notebook/notebook.css | 50 +---
zeppelin-web/src/app/notebook/notebook.html | 55 +---
zeppelin-web/src/index.html | 2 +
zeppelin-web/test/karma.conf.js | 1 +
10 files changed, 131 insertions(+), 308 deletions(-)
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java
index 2727fb4a411..f1a895c8bcf 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java
@@ -67,6 +67,25 @@ public List getUserList(IniRealm r) {
return userList;
}
+
+ /***
+ * Get user roles from shiro.ini
+ * @param r
+ * @return
+ */
+ public List getRolesList(IniRealm r) {
+ List roleList = new ArrayList<>();
+ Map getIniRoles = r.getIni().get("roles");
+ if (getIniRoles != null) {
+ Iterator it = getIniRoles.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry pair = (Map.Entry) it.next();
+ roleList.add(pair.getKey().toString().trim());
+ }
+ }
+ return roleList;
+ }
+
/**
* function to extract users from LDAP
*/
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java
index a079a4460c9..d0b2c52e367 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java
@@ -18,6 +18,7 @@
package org.apache.zeppelin.rest;
+import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.realm.ldap.AbstractLdapRealm;
@@ -29,6 +30,7 @@
import org.apache.zeppelin.server.JsonResponse;
import org.apache.zeppelin.ticket.TicketContainer;
import org.apache.zeppelin.utils.SecurityUtils;
+import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -98,6 +100,7 @@ public Response ticket() {
public Response getUserList(@PathParam("searchText") final String searchText) {
List usersList = new ArrayList<>();
+ List rolesList = new ArrayList<>();
try {
GetUserList getUserListObj = new GetUserList();
Collection realmsList = SecurityUtils.getRealmsList();
@@ -107,6 +110,7 @@ public Response getUserList(@PathParam("searchText") final String searchText) {
String name = realm.getName();
if (name.equals("iniRealm")) {
usersList.addAll(getUserListObj.getUserList((IniRealm) realm));
+ rolesList.addAll(getUserListObj.getRolesList((IniRealm) realm));
} else if (name.equals("ldapRealm")) {
usersList.addAll(getUserListObj.getUserList((JndiLdapRealm) realm, searchText));
} else if (name.equals("activeDirectoryRealm")) {
@@ -120,8 +124,10 @@ public Response getUserList(@PathParam("searchText") final String searchText) {
} catch (Exception e) {
LOG.error("Exception in retrieving Users from realms ", e);
}
- List autoSuggestList = new ArrayList<>();
+ List autoSuggestUserList = new ArrayList<>();
+ List autoSuggestRoleList = new ArrayList<>();
Collections.sort(usersList);
+ Collections.sort(rolesList);
Collections.sort(usersList, new Comparator() {
@Override
public int compare(String o1, String o2) {
@@ -139,13 +145,25 @@ public int compare(String o1, String o2) {
String searchTextLowerCase = searchText.toLowerCase();
if (userLowerCase.indexOf(searchTextLowerCase) != -1) {
maxLength++;
- autoSuggestList.add(usersList.get(i));
+ autoSuggestUserList.add(usersList.get(i));
}
if (maxLength == 5) {
break;
}
}
- return new JsonResponse<>(Response.Status.OK, "", autoSuggestList).build();
+
+ for (String role : rolesList) {
+ if (StringUtils.startsWithIgnoreCase(role, searchText)) {
+ autoSuggestRoleList.add(role);
+ }
+ }
+
+ Map returnListMap = new HashMap<>();
+ returnListMap.put("users", autoSuggestUserList);
+ returnListMap.put("roles", autoSuggestRoleList);
+
+
+ return new JsonResponse<>(Response.Status.OK, "", returnListMap).build();
}
}
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 671b213b471..ea810cbd05e 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
@@ -161,12 +161,12 @@ public void testGroupPermission() throws Exception {
pollingWait(By.xpath("//span[@tooltip='Note permissions']"),
MAX_BROWSER_TIMEOUT_SEC).click();
- pollingWait(By.xpath("//input[@ng-model='permissions.owners']"), MAX_BROWSER_TIMEOUT_SEC)
- .sendKeys("finance");
- pollingWait(By.xpath("//input[@ng-model='permissions.readers']"), MAX_BROWSER_TIMEOUT_SEC)
- .sendKeys("finance");
- pollingWait(By.xpath("//input[@ng-model='permissions.writers']"), MAX_BROWSER_TIMEOUT_SEC)
- .sendKeys("finance");
+ pollingWait(By.xpath(".//*[@id='selectOwners']/following::span//input"),
+ MAX_BROWSER_TIMEOUT_SEC).sendKeys("finance ");
+ pollingWait(By.xpath(".//*[@id='selectReaders']/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)
.sendKeys(Keys.ENTER);
diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json
index 5d849b3490d..ae29f70cca9 100644
--- a/zeppelin-web/bower.json
+++ b/zeppelin-web/bower.json
@@ -31,7 +31,8 @@
"ng-focus-if": "~1.0.2",
"bootstrap3-dialog": "bootstrap-dialog#~1.34.7",
"handsontable": "~0.24.2",
- "moment-duration-format": "^1.3.0"
+ "moment-duration-format": "^1.3.0",
+ "select2": "^4.0.3"
},
"devDependencies": {
"angular-mocks": "1.5.0"
diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js
index 98d6b879f09..20ccfb11f9e 100644
--- a/zeppelin-web/src/app/app.js
+++ b/zeppelin-web/src/app/app.js
@@ -98,6 +98,13 @@
var baseUrlSrv = angular.injector(['zeppelinWebApp']).get('baseUrlSrv');
// withCredentials when running locally via grunt
$http.defaults.withCredentials = true;
+ jQuery.ajaxSetup({
+ dataType: 'json',
+ xhrFields: {
+ withCredentials: true
+ },
+ crossDomain: true
+ });
return $http.get(baseUrlSrv.getRestApiBase() + '/security/ticket').then(function(response) {
zeppelinWebApp.run(function($rootScope) {
$rootScope.ticket = angular.fromJson(response.data).body;
diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js
index dc59f50ae71..00946dd3b64 100644
--- a/zeppelin-web/src/app/notebook/notebook.controller.js
+++ b/zeppelin-web/src/app/notebook/notebook.controller.js
@@ -43,16 +43,6 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
var connectedOnce = false;
// user auto complete related
- $scope.suggestions = [];
- $scope.selectIndex = -1;
- var selectedUser = '';
- var selectedUserIndex = 0;
- var previousSelectedList = [];
- var previousSelectedListOwners = [];
- var previousSelectedListReaders = [];
- var previousSelectedListWriters = [];
- var searchText = [];
- $scope.role = '';
$scope.noteRevisions = [];
$scope.$on('setConnectedStatus', function(event, param) {
@@ -556,6 +546,59 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
success(function(data, status, headers, config) {
$scope.permissions = data.body;
$scope.permissionsOrig = angular.copy($scope.permissions); // to check dirty
+
+ var selectJson = {
+ tokenSeparators: [',', ' '],
+ ajax: {
+ url: function(params) {
+ return baseUrlSrv.getRestApiBase() + '/security/userlist/' + params.term;
+ },
+ delay: 250,
+ processResults: function(data, params) {
+ var results = [];
+
+ if (data.body.users.length !== 0) {
+ var users = [];
+ for (var len = 0; len < data.body.users.length; len++) {
+ users.push({
+ 'id': data.body.users[len],
+ 'text': data.body.users[len]
+ });
+ }
+ results.push({
+ 'text': 'Users :',
+ 'children': users
+ });
+ }
+ if (data.body.roles.length !== 0) {
+ var roles = [];
+ for (var len = 0; len < data.body.roles.length; len++) {
+ roles.push({
+ 'id': data.body.roles[len],
+ 'text': data.body.roles[len]
+ });
+ }
+ results.push({
+ 'text': 'Roles :',
+ 'children': roles
+ });
+ }
+ return {
+ results: results,
+ pagination: {
+ more: false
+ }
+ };
+ },
+ cache: false
+ },
+ width: ' ',
+ tags: true
+ };
+
+ angular.element('#selectOwners').select2(selectJson);
+ angular.element('#selectReaders').select2(selectJson);
+ angular.element('#selectWriters').select2(selectJson);
if (callback) {
callback();
}
@@ -592,15 +635,9 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
};
function convertPermissionsToArray() {
- if (!angular.isArray($scope.permissions.owners)) {
- $scope.permissions.owners = $scope.permissions.owners.split(',');
- }
- if (!angular.isArray($scope.permissions.readers)) {
- $scope.permissions.readers = $scope.permissions.readers.split(',');
- }
- if (!angular.isArray($scope.permissions.writers)) {
- $scope.permissions.writers = $scope.permissions.writers.split(',');
- }
+ $scope.permissions.owners = angular.element('#selectOwners').val();
+ $scope.permissions.readers = angular.element('#selectReaders').val();
+ $scope.permissions.writers = angular.element('#selectWriters').val();
}
$scope.savePermissions = function() {
@@ -652,6 +689,9 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
$scope.togglePermissions = function() {
if ($scope.showPermissions) {
$scope.closePermissions();
+ angular.element('#selectOwners').select2({});
+ angular.element('#selectReaders').select2({});
+ angular.element('#selectWriters').select2({});
} else {
$scope.openPermissions();
$scope.closeSetting();
@@ -674,195 +714,7 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
}
};
- function checkPreviousRole(role) {
- var i = 0;
- if (role !== $scope.role) {
- if ($scope.role === 'owners') {
- previousSelectedListOwners = [];
- for (i = 0; i < previousSelectedList.length; i++) {
- previousSelectedListOwners[i] = previousSelectedList[i];
- }
- }
- if ($scope.role === 'readers') {
- previousSelectedListReaders = [];
- for (i = 0; i < previousSelectedList.length; i++) {
- previousSelectedListReaders[i] = previousSelectedList[i];
- }
- }
- if ($scope.role === 'writers') {
- previousSelectedListWriters = [];
- for (i = 0; i < previousSelectedList.length; i++) {
- previousSelectedListWriters[i] = previousSelectedList[i];
- }
- }
-
- $scope.role = role;
- previousSelectedList = [];
- if (role === 'owners') {
- for (i = 0; i < previousSelectedListOwners.length; i++) {
- previousSelectedList[i] = previousSelectedListOwners[i];
- }
- }
- if (role === 'readers') {
- for (i = 0; i < previousSelectedListReaders.length; i++) {
- previousSelectedList[i] = previousSelectedListReaders[i];
- }
- }
- if (role === 'writers') {
- for (i = 0; i < previousSelectedListWriters.length; i++) {
- previousSelectedList[i] = previousSelectedListWriters[i];
- }
- }
- }
- }
-
- function convertToArray(role) {
- if (!$scope.permissions) {
- return;
- } else if (role === 'owners' && typeof $scope.permissions.owners === 'string') {
- searchText = $scope.permissions.owners.split(',');
- } else if (role === 'readers' && typeof $scope.permissions.readers === 'string') {
- searchText = $scope.permissions.readers.split(',');
- } else if (role === 'writers' && typeof $scope.permissions.writers === 'string') {
- searchText = $scope.permissions.writers.split(',');
- }
-
- for (var i = 0; i < searchText.length; i++) {
- searchText[i] = searchText[i].trim();
- }
- }
-
- function convertToString(role) {
- if (role === 'owners') {
- $scope.permissions.owners = searchText.join();
- } else if (role === 'readers') {
- $scope.permissions.readers = searchText.join();
- } else if (role === 'writers') {
- $scope.permissions.writers = searchText.join();
- }
- }
-
- function getSuggestions(searchQuery) {
- $scope.suggestions = [];
- $http.get(baseUrlSrv.getRestApiBase() + '/security/userlist/' + searchQuery).then(function
- (response) {
- var userlist = angular.fromJson(response.data).body;
- for (var k in userlist) {
- $scope.suggestions.push(userlist[k]);
- }
- });
- }
-
- function updatePreviousList() {
- for (var i = 0; i < searchText.length; i++) {
- previousSelectedList[i] = searchText[i];
- }
- }
-
- var getChangedIndex = function() {
- if (previousSelectedList.length === 0) {
- selectedUserIndex = searchText.length - 1;
- } else {
- for (var i = 0; i < searchText.length; i++) {
- if (previousSelectedList[i] !== searchText[i]) {
- selectedUserIndex = i;
- previousSelectedList = [];
- break;
- }
- }
- }
- updatePreviousList();
- };
-
- $scope.$watch('permissions.owners', _.debounce(function(readers) {
- $scope.$apply(function() {
- $scope.search('owners');
- });
- }, 350));
-
- $scope.$watch('permissions.readers', _.debounce(function(readers) {
- $scope.$apply(function() {
- $scope.search('readers');
- });
- }, 350));
-
- $scope.$watch('permissions.writers', _.debounce(function(readers) {
- $scope.$apply(function() {
- $scope.search('writers');
- });
- }, 350));
-
- // function to find suggestion list on change
- $scope.search = function(role) {
- angular.element('.userlist').show();
- convertToArray(role);
- checkPreviousRole(role);
- getChangedIndex();
- $scope.selectIndex = -1;
- $scope.suggestions = [];
- selectedUser = searchText[selectedUserIndex];
- if (selectedUser !== '') {
- getSuggestions(selectedUser);
- } else {
- $scope.suggestions = [];
- }
- };
-
- var checkIfSelected = function() {
- if (($scope.suggestions.length === 0) &&
- ($scope.selectIndex < 0 || $scope.selectIndex >= $scope.suggestions.length) ||
- ($scope.suggestions.length !== 0 && ($scope.selectIndex < 0 || $scope.selectIndex >= $scope.suggestions.length))
- ) {
- searchText[selectedUserIndex] = selectedUser;
- $scope.suggestions = [];
- return true;
- } else {
- return false;
- }
- };
-
- $scope.checkKeyDown = function(event, role) {
- if (event.keyCode === 40) {
- event.preventDefault();
- if ($scope.selectIndex + 1 !== $scope.suggestions.length) {
- $scope.selectIndex++;
- }
- } else if (event.keyCode === 38) {
- event.preventDefault();
-
- if ($scope.selectIndex - 1 !== -1) {
- $scope.selectIndex--;
-
- }
- } else if (event.keyCode === 13) {
- event.preventDefault();
- if (!checkIfSelected()) {
- selectedUser = $scope.suggestions[$scope.selectIndex];
- searchText[selectedUserIndex] = $scope.suggestions[$scope.selectIndex];
- updatePreviousList();
- convertToString(role);
- $scope.suggestions = [];
- }
- }
- };
-
- $scope.checkKeyUp = function(event) {
- if (event.keyCode !== 8 || event.keyCode !== 46) {
- if (searchText[selectedUserIndex] === '') {
- $scope.suggestions = [];
- }
- }
- };
-
- $scope.assignValueAndHide = function(index, role) {
- searchText[selectedUserIndex] = $scope.suggestions[index];
- updatePreviousList();
- convertToString(role);
- $scope.suggestions = [];
- };
-
angular.element(document).click(function() {
- angular.element('.userlist').hide();
angular.element('.ace_autocomplete').hide();
});
diff --git a/zeppelin-web/src/app/notebook/notebook.css b/zeppelin-web/src/app/notebook/notebook.css
index ed45c67bc70..c11544fd0d2 100644
--- a/zeppelin-web/src/app/notebook/notebook.css
+++ b/zeppelin-web/src/app/notebook/notebook.css
@@ -308,51 +308,7 @@
cursor: default;
}
-.userlist {
- width: 230px;
- font-family: Georgia, Times, serif;
- font-size: 15px;
- position: absolute;
- z-index: 9999;
-}
-
-.userlist ul {
- list-style: none;
-}
-
-.userlist ul li {
- box-shadow: 3px 3px 5px #888888;
- display: list-item;
- text-decoration: none;
- color: #000000;
- background-color: #FFFFFF;
- line-height: 30px;
- border-bottom-style: none;
- border-bottom-width: 1px;
- border-bottom:1px #CCCCCC solid;
- padding-left: 10px;
- cursor: pointer;
-}
-
-.userlist ul li:first-child {
- border-top-right-radius: 5px;
- border-top-left-radius: 5px;
-}
-
-.userlist ul li:last-child {
- border-bottom-right-radius: 5px;
- border-bottom-left-radius: 5px;
-}
-
-.userlist ul li strong {
- margin-right: 10px;
-}
-
-.userlist li:hover {
- background-color: #E0E0E0;
-}
-
-.userlist li:active,
-.userlist li.active {
- background-color: #428BCA;
+.select2-container--default{
+ min-width: 150px;
+ max-width: 50%;
}
diff --git a/zeppelin-web/src/app/notebook/notebook.html b/zeppelin-web/src/app/notebook/notebook.html
index fd329ac18e8..9ad71662305 100644
--- a/zeppelin-web/src/app/notebook/notebook.html
+++ b/zeppelin-web/src/app/notebook/notebook.html
@@ -70,57 +70,24 @@ Note Permissions (Only note owners can change)
diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html
index 9fe9489501c..ff1fa91feeb 100644
--- a/zeppelin-web/src/index.html
+++ b/zeppelin-web/src/index.html
@@ -48,6 +48,7 @@
+
@@ -144,6 +145,7 @@
+
diff --git a/zeppelin-web/test/karma.conf.js b/zeppelin-web/test/karma.conf.js
index 778f0f151fd..f9f03a413fa 100644
--- a/zeppelin-web/test/karma.conf.js
+++ b/zeppelin-web/test/karma.conf.js
@@ -64,6 +64,7 @@ module.exports = function(config) {
'bower_components/pikaday/pikaday.js',
'bower_components/handsontable/dist/handsontable.js',
'bower_components/moment-duration-format/lib/moment-duration-format.js',
+ 'bower_components/select2/dist/js/select2.js',
'bower_components/angular-mocks/angular-mocks.js',
// endbower
'src/app/app.js',
From 17e17a9a47c5779a2e083e5a54be1cdd8ce74079 Mon Sep 17 00:00:00 2001
From: Prabhjyot Singh
Date: Thu, 28 Jul 2016 00:25:31 +0530
Subject: [PATCH 2/2] implement @r-kamath feedback
---
.../org/apache/zeppelin/rest/SecurityRestApi.java | 12 ++++--------
.../apache/zeppelin/rest/SecurityRestApiTest.java | 4 ++--
zeppelin-web/src/app/notebook/notebook.controller.js | 6 +++++-
3 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java
index d0b2c52e367..7af52c8c8ba 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java
@@ -21,7 +21,6 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.jdbc.JdbcRealm;
-import org.apache.shiro.realm.ldap.AbstractLdapRealm;
import org.apache.shiro.realm.ldap.JndiLdapRealm;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.zeppelin.annotation.ZeppelinApi;
@@ -30,7 +29,6 @@
import org.apache.zeppelin.server.JsonResponse;
import org.apache.zeppelin.ticket.TicketContainer;
import org.apache.zeppelin.utils.SecurityUtils;
-import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -140,12 +138,10 @@ public int compare(String o1, String o2) {
}
});
int maxLength = 0;
- for (int i = 0; i < usersList.size(); i++) {
- String userLowerCase = usersList.get(i).toLowerCase();
- String searchTextLowerCase = searchText.toLowerCase();
- if (userLowerCase.indexOf(searchTextLowerCase) != -1) {
+ for (String user : usersList) {
+ if (StringUtils.containsIgnoreCase(user, searchText)) {
+ autoSuggestUserList.add(user);
maxLength++;
- autoSuggestUserList.add(usersList.get(i));
}
if (maxLength == 5) {
break;
@@ -153,7 +149,7 @@ public int compare(String o1, String o2) {
}
for (String role : rolesList) {
- if (StringUtils.startsWithIgnoreCase(role, searchText)) {
+ if (StringUtils.containsIgnoreCase(role, searchText)) {
autoSuggestRoleList.add(role);
}
}
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/SecurityRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/SecurityRestApiTest.java
index 54c31c1fd6a..b4ecd97e375 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/SecurityRestApiTest.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/SecurityRestApiTest.java
@@ -69,7 +69,7 @@ public void testGetUserList() throws IOException {
get.addRequestHeader("Origin", "http://localhost");
Map resp = gson.fromJson(get.getResponseBodyAsString(),
new TypeToken