diff --git a/docs/assets/themes/zeppelin/img/docs-img/add_credential.png b/docs/assets/themes/zeppelin/img/docs-img/add_credential.png
new file mode 100644
index 00000000000..dcf446074d0
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/add_credential.png differ
diff --git a/docs/assets/themes/zeppelin/img/docs-img/credential_tab.png b/docs/assets/themes/zeppelin/img/docs-img/credential_tab.png
new file mode 100644
index 00000000000..66a1fbed56e
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/credential_tab.png differ
diff --git a/docs/index.md b/docs/index.md
index e120e7ae1ee..141e7f6aeef 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -167,7 +167,7 @@ Join to our [Mailing list](https://zeppelin.apache.org/community.html) and repor
* [Authentication for NGINX](./security/authentication.html)
* [Shiro Authentication](./security/shiroauthentication.html)
* [Notebook Authorization](./security/notebook_authorization.html)
- * [Interpreter & Data Resource Authorization](./security/interpreter_authorization.html)
+ * [Data Source Authorization](./security/datasource_authorization.html)
* Contribute
* [Writing Zeppelin Interpreter](./development/writingzeppelininterpreter.html)
* [Writing Zeppelin Application (Experimental)](./development/writingzeppelinapplication.html)
diff --git a/docs/security/datasource_authorization.md b/docs/security/datasource_authorization.md
new file mode 100644
index 00000000000..3f86b8ab00d
--- /dev/null
+++ b/docs/security/datasource_authorization.md
@@ -0,0 +1,59 @@
+---
+layout: page
+title: "Data Source Authorization"
+description: "Data Source Authorization"
+group: security
+---
+
+# Data Source Authorization in Apache Zeppelin
+
+
+
+## Overview
+
+Data source authorization involves authenticating to the data source like a Mysql database and letting it determine user permissions.
+Apache Zeppelin allows users to use their own credentials to authenticate with **Data Sources**.
+
+For example, let's assume you have an account in the Vertica databases with credentials.
+You might want to use this account to create a JDBC connection instead of a shared account with all users who are defined in `conf/shiro.ini`.
+In this case, you can add your credential information to Apache Zeppelin and use them with below simple steps.
+
+## How to save the credential information?
+You can add new credentials in the dropdown menu for your data source which can be passed to interpreters.
+
+
+
+**Entity** can be the key that distinguishes each credential sets. Type **Username & Password** for your own credentials. ex) user & password of Mysql
+
+
+
+The credentials saved as per users defined in `conf/shiro.ini`.
+If you didn't activate [shiro authentication in Apache Zeppelin](./shiroauthentication.html), your credential information will be saved as `anonymous`.
+All credential information also can be found in `conf/credentials.json`.
+
+#### JDBC interpreter
+You need to maintain per-user connection pools.
+The interpret method takes the user string as a parameter and executes the jdbc call using a connection in the user's connection pool.
+
+#### Presto
+You don't need a password if the Presto DB server runs backend code using HDFS authorization for the user.
+
+#### Vertica and Mysql
+You have to store the password information for users.
+
+## Please note
+As a first step of data source authentication feature, [ZEPPELIN-828](https://issues.apache.org/jira/browse/ZEPPELIN-828) was proposed and implemented in Pull Request [#860](https://github.com/apache/zeppelin/pull/860).
+Currently, only customized 3rd party interpreters can use this feature. We are planning to apply this mechanism to [the community interpreters](../manual/interpreterinstallation.md#available-community-managed-interpreters) in the near future.
+Please keep track [ZEPPELIN-1070](https://issues.apache.org/jira/browse/ZEPPELIN-1070).
diff --git a/docs/security/interpreter_authorization.md b/docs/security/interpreter_authorization.md
deleted file mode 100644
index 6e59e0718a9..00000000000
--- a/docs/security/interpreter_authorization.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-layout: page
-title: "Notebook Authorization"
-description: "Notebook Authorization"
-group: security
----
-
-# Interpreter and Data Source Authorization
-
-
-
-## Interpreter Authorization
-
-Interpreter authorization involves permissions like creating an interpreter and execution queries using it.
-
-## Data Source Authorization
-
-Data source authorization involves authenticating to the data source like a Mysql database and letting it determine user permissions.
-
-For the JDBC interpreter, we need to maintain per-user connection pools.
-The interpret method takes the user string as parameter and executes the jdbc call using a connection in the user's connection pool.
-
-In case of Presto, we don't need password if the Presto DB server runs backend code using HDFS authorization for the user.
-For databases like Vertica and Mysql we have to store password information for users.
-
-The Credentials tab in the navbar allows users to save credentials for data sources which are passed to interpreters.
diff --git a/zeppelin-distribution/src/bin_license/LICENSE b/zeppelin-distribution/src/bin_license/LICENSE
index 292b8d38b9b..d7cc1ccd60d 100644
--- a/zeppelin-distribution/src/bin_license/LICENSE
+++ b/zeppelin-distribution/src/bin_license/LICENSE
@@ -124,7 +124,7 @@ The text of each license is also included at licenses/LICENSE-[project]-[version
(The MIT License) angular-elastic v2.4.2 (https://github.com/monospaced/angular-elastic) - https://github.com/monospaced/angular-elastic/blob/v2.4.2/LICENCE.txt
(The MIT License) angular-elastic-input v2.2.0 (https://github.com/jacek-pulit/angular-elastic-input) - https://github.com/jacek-pulit/angular-elastic-input/blob/v2.2.0/LICENSE
(The MIT License) ng-focus-if v1.0.2 (https://github.com/hiebj/ng-focus-if) - https://github.com/hiebj/ng-focus-if/blob/v1.0.2/LICENSE
- (The MIT License) angular-xeditable v0.1.8 (http://vitalets.github.io/angular-xeditable/) - https://github.com/vitalets/angular-xeditable/tree/0.1.8
+ (The MIT License) angular-xeditable v0.1.12 (http://vitalets.github.io/angular-xeditable/) - https://github.com/vitalets/angular-xeditable/tree/0.1.12
(The MIT License) lodash v3.9.3 (https://lodash.com/) - https://github.com/lodash/lodash/blob/3.9.3/LICENSE.txt
(The MIT License) angular-filter v0.5.4 (https://github.com/a8m/angular-filter) - https://github.com/a8m/angular-filter/blob/v0.5.4/license.md
(The MIT License) ngToast v2.0.0 (http://tamerayd.in/ngToast/) - http://tameraydin.mit-license.org/
diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json
index a414653cbbf..94d228a0eaa 100644
--- a/zeppelin-web/bower.json
+++ b/zeppelin-web/bower.json
@@ -23,7 +23,7 @@
"ng-sortable": "~1.3.3",
"angular-elastic": "~2.4.2",
"angular-elastic-input": "~2.2.0",
- "angular-xeditable": "0.1.8",
+ "angular-xeditable": "0.1.12",
"highlightjs": "^9.2.0",
"lodash": "~3.9.3",
"angular-filter": "~0.5.4",
diff --git a/zeppelin-web/src/app/credential/credential.controller.js b/zeppelin-web/src/app/credential/credential.controller.js
index df231635b40..621e499ce7f 100644
--- a/zeppelin-web/src/app/credential/credential.controller.js
+++ b/zeppelin-web/src/app/credential/credential.controller.js
@@ -15,42 +15,134 @@
'use strict';
angular.module('zeppelinWebApp').controller('CredentialCtrl', function($scope, $route, $routeParams, $location,
- $rootScope, $http, baseUrlSrv) {
+ $rootScope, $http, baseUrlSrv, ngToast) {
$scope._ = _;
- $scope.credentialEntity = '';
- $scope.credentialUsername = '';
- $scope.credentialPassword = '';
+ $scope.credentialInfo = [];
+ $scope.showAddNewCredentialInfo = false;
- $scope.updateCredentials = function() {
- if (_.isEmpty($scope.credentialEntity.trim()) ||
- _.isEmpty($scope.credentialUsername.trim())) {
- BootstrapDialog.alert({
- closable: true,
- message: 'Username \\ Entity can not be empty.'
+ var getCredentialInfo = function() {
+ $http.get(baseUrlSrv.getRestApiBase() + '/credential').
+ success(function(data, status, headers, config) {
+ $scope.credentialInfo = _.map(data.body.userCredentials, function(value, prop) {
+ return {entity: prop, password: value.password, username: value.username};
+ });
+ console.log('Success %o %o', status, $scope.credentialInfo);
+ }).
+ error(function(data, status, headers, config) {
+ console.log('Error %o %o', status, data.message);
+ });
+ };
+
+ $scope.addNewCredentialInfo = function() {
+ if ($scope.entity && _.isEmpty($scope.entity.trim()) &&
+ $scope.username && _.isEmpty($scope.username.trim())) {
+ ngToast.danger({
+ content: 'Username \\ Entity can not be empty.',
+ verticalPosition: 'bottom',
+ timeout: '3000'
});
return;
}
- $http.put(baseUrlSrv.getRestApiBase() + '/credential',
- {'entity': $scope.credentialEntity,
- 'username': $scope.credentialUsername,
- 'password': $scope.credentialPassword
- }).
+ var newCredential = {
+ 'entity': $scope.entity,
+ 'username': $scope.username,
+ 'password': $scope.password
+ };
+
+ $http.put(baseUrlSrv.getRestApiBase() + '/credential', newCredential).
success(function(data, status, headers, config) {
- BootstrapDialog.alert({
- closable: true,
- message: 'Successfully saved credentials.'
+ ngToast.success({
+ content: 'Successfully saved credentials.',
+ verticalPosition: 'bottom',
+ timeout: '3000'
});
- $scope.credentialEntity = '';
- $scope.credentialUsername = '';
- $scope.credentialPassword = '';
+ $scope.credentialInfo.push(newCredential);
+ resetCredentialInfo();
+ $scope.showAddNewCredentialInfo = false;
console.log('Success %o %o', status, data.message);
}).
error(function(data, status, headers, config) {
- alert('Error saving credentials');
+ ngToast.danger({
+ content: 'Error saving credentials',
+ verticalPosition: 'bottom',
+ timeout: '3000'
+ });
console.log('Error %o %o', status, data.message);
});
};
+ $scope.cancelCredentialInfo = function() {
+ $scope.showAddNewCredentialInfo = false;
+ resetCredentialInfo();
+ };
+
+ var resetCredentialInfo = function() {
+ $scope.entity = '';
+ $scope.username = '';
+ $scope.password = '';
+ };
+
+ $scope.copyOriginCredentialsInfo = function() {
+ ngToast.info({
+ content: 'Since entity is a unique key, you can edit only username & password',
+ verticalPosition: 'bottom',
+ timeout: '3000'
+ });
+ };
+
+ $scope.updateCredentialInfo = function(form, data, entity) {
+ var request = {
+ entity: entity,
+ username: data.username,
+ password: data.password
+ };
+
+ $http.put(baseUrlSrv.getRestApiBase() + '/credential/', request).
+ success(function(data, status, headers, config) {
+ var index = _.findIndex($scope.credentialInfo, {'entity': entity});
+ $scope.credentialInfo[index] = request;
+ return true;
+ }).
+ error(function(data, status, headers, config) {
+ console.log('Error %o %o', status, data.message);
+ ngToast.danger({
+ content: 'We couldn\'t save the credential',
+ verticalPosition: 'bottom',
+ timeout: '3000'
+ });
+ form.$show();
+ });
+ return false;
+ };
+
+ $scope.removeCredentialInfo = function(entity) {
+ BootstrapDialog.confirm({
+ closable: false,
+ closeByBackdrop: false,
+ closeByKeyboard: false,
+ title: '',
+ message: 'Do you want to delete this credential information?',
+ callback: function(result) {
+ if (result) {
+ $http.delete(baseUrlSrv.getRestApiBase() + '/credential/' + entity).
+ success(function(data, status, headers, config) {
+ var index = _.findIndex($scope.credentialInfo, {'entity': entity});
+ $scope.credentialInfo.splice(index, 1);
+ console.log('Success %o %o', status, data.message);
+ }).
+ error(function(data, status, headers, config) {
+ console.log('Error %o %o', status, data.message);
+ });
+ }
+ }
+ });
+ };
+
+ var init = function() {
+ getCredentialInfo();
+ };
+
+ init();
});
diff --git a/zeppelin-web/src/app/credential/credential.html b/zeppelin-web/src/app/credential/credential.html
index 46d83d90757..d4131b19be6 100644
--- a/zeppelin-web/src/app/credential/credential.html
+++ b/zeppelin-web/src/app/credential/credential.html
@@ -18,53 +18,134 @@