diff --git a/.gitignore b/.gitignore index 532891481d9..e13f0e94d47 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ conf/truststore conf/interpreter.json conf/notebook-authorization.json conf/shiro.ini +conf/credentials.json # other generated files spark/dependency-reduced-pom.xml diff --git a/docs/security/interpreter_authorization.md b/docs/security/interpreter_authorization.md index d1c792d113f..862ef9b828b 100644 --- a/docs/security/interpreter_authorization.md +++ b/docs/security/interpreter_authorization.md @@ -32,3 +32,5 @@ The interpret method takes the user string as parameter and executes the jdbc ca 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. \ No newline at end of file diff --git a/pom.xml b/pom.xml index 833f219dc37..6dba9d760d3 100755 --- a/pom.xml +++ b/pom.xml @@ -471,6 +471,7 @@ **/zeppelin-distribution/src/bin_license/** conf/interpreter.json conf/notebook-authorization.json + conf/credentials.json conf/zeppelin-env.sh spark-*-bin*/** .spark-dist/** diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/AuthenticationInfo.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/AuthenticationInfo.java index 5d54342c531..c4cd441497a 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/AuthenticationInfo.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/AuthenticationInfo.java @@ -24,6 +24,7 @@ public class AuthenticationInfo { String user; String ticket; + UserCredentials userCredentials; public AuthenticationInfo() {} @@ -52,4 +53,13 @@ public String getTicket() { public void setTicket(String ticket) { this.ticket = ticket; } + + public UserCredentials getUserCredentials() { + return userCredentials; + } + + public void setUserCredentials(UserCredentials userCredentials) { + this.userCredentials = userCredentials; + } + } diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/Credentials.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/Credentials.java new file mode 100644 index 00000000000..b3a3726f845 --- /dev/null +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/Credentials.java @@ -0,0 +1,122 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You 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. +*/ + +package org.apache.zeppelin.user; + + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +/** + * Class defining credentials for data source authorization + */ +public class Credentials { + private static final Logger LOG = LoggerFactory.getLogger(Credentials.class); + + private Map credentialsMap; + private Gson gson; + private Boolean credentialsPersist = true; + File credentialsFile; + + public Credentials(Boolean credentialsPersist, String credentialsPath) { + this.credentialsPersist = credentialsPersist; + if (credentialsPath != null) { + credentialsFile = new File(credentialsPath); + } + credentialsMap = new HashMap<>(); + if (credentialsPersist) { + GsonBuilder builder = new GsonBuilder(); + builder.setPrettyPrinting(); + gson = builder.create(); + loadFromFile(); + } + } + + public UserCredentials getUserCredentials(String username) { + UserCredentials uc = credentialsMap.get(username); + if (uc == null) { + uc = new UserCredentials(); + } + return uc; + } + + public void putUserCredentials(String username, UserCredentials uc) throws IOException { + credentialsMap.put(username, uc); + if (credentialsPersist) { + saveToFile(); + } + } + + private void loadFromFile() { + LOG.info(credentialsFile.getAbsolutePath()); + if (!credentialsFile.exists()) { + // nothing to read + return; + } + + try { + FileInputStream fis = new FileInputStream(credentialsFile); + InputStreamReader isr = new InputStreamReader(fis); + BufferedReader bufferedReader = new BufferedReader(isr); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + sb.append(line); + } + isr.close(); + fis.close(); + + String json = sb.toString(); + CredentialsInfoSaving info = gson.fromJson(json, CredentialsInfoSaving.class); + this.credentialsMap = info.credentialsMap; + } catch (IOException e) { + LOG.error("Error loading credentials file", e); + e.printStackTrace(); + } + } + + private void saveToFile() throws IOException { + String jsonString; + + synchronized (credentialsMap) { + CredentialsInfoSaving info = new CredentialsInfoSaving(); + info.credentialsMap = credentialsMap; + jsonString = gson.toJson(info); + } + + try { + if (!credentialsFile.exists()) { + credentialsFile.createNewFile(); + } + + FileOutputStream fos = new FileOutputStream(credentialsFile, false); + OutputStreamWriter out = new OutputStreamWriter(fos); + out.append(jsonString); + out.close(); + fos.close(); + } catch (IOException e) { + LOG.error("Error saving credentials file", e); + } + } + +} diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/CredentialsInfoSaving.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/CredentialsInfoSaving.java new file mode 100644 index 00000000000..20b586cc4e2 --- /dev/null +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/CredentialsInfoSaving.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package org.apache.zeppelin.user; + +import java.util.Map; + +/** + * Helper class to save credentials + */ +public class CredentialsInfoSaving { + public Map credentialsMap; +} diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/UserCredentials.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/UserCredentials.java new file mode 100644 index 00000000000..166840a68ef --- /dev/null +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/UserCredentials.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package org.apache.zeppelin.user; + +import java.util.HashMap; +import java.util.Map; + +/** + * User Credentials POJO + */ +public class UserCredentials { + private Map userCredentials; + + public UserCredentials() { + this.userCredentials = new HashMap<>(); + } + + public UsernamePassword getUsernamePassword(String entity) { + return userCredentials.get(entity); + } + + public void putUsernamePassword(String entity, UsernamePassword up) { + userCredentials.put(entity, up); + } + + @Override + public String toString() { + return "UserCredentials{" + + "userCredentials=" + userCredentials + + '}'; + } +} diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/UsernamePassword.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/UsernamePassword.java new file mode 100644 index 00000000000..00116a990ad --- /dev/null +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/UsernamePassword.java @@ -0,0 +1,55 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You 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. +*/ + +package org.apache.zeppelin.user; + +/** + * Username and Password POJO + */ +public class UsernamePassword { + private String username; + private String password; + + public UsernamePassword(String username, String password) { + this.username = username; + this.password = password; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public String toString() { + return "UsernamePassword{" + + "username='" + username + '\'' + + ", password='" + password + '\'' + + '}'; + } +} diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/user/CredentialsTest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/user/CredentialsTest.java new file mode 100644 index 00000000000..259516f9766 --- /dev/null +++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/user/CredentialsTest.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package org.apache.zeppelin.user; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import java.io.IOException; + +public class CredentialsTest { + + @Test + public void testDefaultProperty() throws IOException { + Credentials credentials = new Credentials(false, null); + UserCredentials userCredentials = new UserCredentials(); + UsernamePassword up1 = new UsernamePassword("user2", "password"); + userCredentials.putUsernamePassword("hive(vertica)", up1); + credentials.putUserCredentials("user1", userCredentials); + UserCredentials uc2 = credentials.getUserCredentials("user1"); + UsernamePassword up2 = uc2.getUsernamePassword("hive(vertica)"); + assertEquals(up1.getUsername(), up2.getUsername()); + assertEquals(up1.getPassword(), up2.getPassword()); + } +} diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/CredentialRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/CredentialRestApi.java new file mode 100644 index 00000000000..b1a4d17a837 --- /dev/null +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/CredentialRestApi.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package org.apache.zeppelin.rest; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import org.apache.zeppelin.user.Credentials; +import org.apache.zeppelin.user.UserCredentials; +import org.apache.zeppelin.user.UsernamePassword; +import org.apache.zeppelin.server.JsonResponse; +import org.apache.zeppelin.utils.SecurityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import java.io.IOException; +import java.util.Map; + +/** + * Credential Rest API + * + */ +@Path("/credential") +@Produces("application/json") +public class CredentialRestApi { + Logger logger = LoggerFactory.getLogger(CredentialRestApi.class); + private Credentials credentials; + private Gson gson = new Gson(); + + @Context + private HttpServletRequest servReq; + + public CredentialRestApi() { + + } + + public CredentialRestApi(Credentials credentials) { + this.credentials = credentials; + } + + /** + * Update credentials for current user + */ + @PUT + public Response putCredentials(String message) throws IOException { + Map messageMap = gson.fromJson(message, + new TypeToken>(){}.getType()); + String entity = messageMap.get("entity"); + String username = messageMap.get("username"); + String password = messageMap.get("password"); + String user = SecurityUtils.getPrincipal(); + logger.info("Update credentials for user {} entity {}", user, entity); + UserCredentials uc = credentials.getUserCredentials(user); + uc.putUsernamePassword(entity, new UsernamePassword(username, password)); + credentials.putUserCredentials(user, uc); + return new JsonResponse(Status.OK, "", "").build(); + } + +} diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java index c8fc60a46b4..7412611532b 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java @@ -31,6 +31,7 @@ import org.apache.zeppelin.search.LuceneSearch; import org.apache.zeppelin.search.SearchService; import org.apache.zeppelin.socket.NotebookServer; +import org.apache.zeppelin.user.Credentials; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.server.*; import org.eclipse.jetty.server.handler.ContextHandlerCollection; @@ -67,6 +68,7 @@ public class ZeppelinServer extends Application { private NotebookRepo notebookRepo; private SearchService notebookIndex; private NotebookAuthorization notebookAuthorization; + private Credentials credentials; private DependencyResolver depResolver; public ZeppelinServer() throws Exception { @@ -80,9 +82,10 @@ public ZeppelinServer() throws Exception { this.notebookRepo = new NotebookRepoSync(conf); this.notebookIndex = new LuceneSearch(); this.notebookAuthorization = new NotebookAuthorization(conf); + this.credentials = new Credentials(conf.credentialsPersist(), conf.getCredentialsPath()); notebook = new Notebook(conf, notebookRepo, schedulerFactory, replFactory, notebookWsServer, - notebookIndex, notebookAuthorization); + notebookIndex, notebookAuthorization, credentials); } public static void main(String[] args) throws InterruptedException { @@ -292,6 +295,9 @@ public Set getSingletons() { InterpreterRestApi interpreterApi = new InterpreterRestApi(replFactory); singletons.add(interpreterApi); + CredentialRestApi credentialApi = new CredentialRestApi(credentials); + singletons.add(credentialApi); + SecurityRestApi securityApi = new SecurityRestApi(); singletons.add(securityApi); diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js index 16fad36c4dd..bc8e52c9a03 100644 --- a/zeppelin-web/src/app/app.js +++ b/zeppelin-web/src/app/app.js @@ -66,6 +66,10 @@ templateUrl: 'app/interpreter/interpreter.html', controller: 'InterpreterCtrl' }) + .when('/credential', { + templateUrl: 'app/credential/credential.html', + controller: 'CredentialCtrl' + }) .when('/configuration', { templateUrl: 'app/configuration/configuration.html', controller: 'ConfigurationCtrl' diff --git a/zeppelin-web/src/app/credential/credential.controller.js b/zeppelin-web/src/app/credential/credential.controller.js new file mode 100644 index 00000000000..4bb89f044d5 --- /dev/null +++ b/zeppelin-web/src/app/credential/credential.controller.js @@ -0,0 +1,40 @@ +/* jshint loopfunc: true */ +/* + * 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').controller('CredentialCtrl', function($scope, $route, $routeParams, $location, $rootScope, + $http, baseUrlSrv) { + $scope._ = _; + + $scope.updateCredentials = function() { + $http.put(baseUrlSrv.getRestApiBase() + '/credential', + { 'entity': $scope.credentialEntity, + 'username': $scope.credentialUsername, + 'password': $scope.credentialPassword + } ). + success(function (data, status, headers, config) { + alert('Successfully saved credentials'); + $scope.credentialEntity = ''; + $scope.credentialUsername = ''; + $scope.credentialPassword = ''; + console.log('Success %o %o', status, data.message); + }). + error(function (data, status, headers, config) { + alert('Error saving credentials'); + console.log('Error %o %o', status, data.message); + }); + }; + +}); diff --git a/zeppelin-web/src/app/credential/credential.css b/zeppelin-web/src/app/credential/credential.css new file mode 100644 index 00000000000..b6fe133fcf5 --- /dev/null +++ b/zeppelin-web/src/app/credential/credential.css @@ -0,0 +1,86 @@ +/* + * 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. + */ + +.interpreterHead { + margin: -10px -10px 20px; + padding: 10px 15px 15px 15px; + background: white; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); + border-bottom: 1px solid #E5E5E5; +} + +.interpreterHead .header { + font-family: 'Roboto', sans-serif; +} + +.interpreterHead textarea, +.interpreter textarea { + width: 100%; + display: block; + height: 20px; + resize: none; + border: 1px solid #CCCCCC; + font-size: 12px; +} + +.interpreter .interpreter-title { + font-size: 20px; + font-weight: bold; + color: #3071a9; + float: left; + margin-top: 0; +} + +.interpreter ul { + margin: 0; + padding: 0; +} + +.interpreter .interpreterInfo { + list-style-type: none; +} + +.interpreter table tr .interpreterPropertyKey { + padding : 5px 5px 5px 5px; +} + +.interpreter table tr .interpreterPropertyValue { + padding : 5px 5px 5px 5px; + display: block; + max-height: 100px; + overflow-y: auto; +} + +.interpreterSettingAdd { + margin : 5px 5px 5px 5px; + padding : 10px 10px 10px 10px; +} + +.editable-wrap { + width : 100%; +} + +.interpreter h5 { + font-weight: bold; +} + +.new_h3 { + margin-top: 1px; + padding-top: 7px; + float: left; +} + +.empty-properties-message { + color: #666; +} diff --git a/zeppelin-web/src/app/credential/credential.html b/zeppelin-web/src/app/credential/credential.html new file mode 100644 index 00000000000..338845f5769 --- /dev/null +++ b/zeppelin-web/src/app/credential/credential.html @@ -0,0 +1,71 @@ + +
+
+
+
+

+ Credentials +

+
+
+
+
+ Add credentials for entities one at a time.
+
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + +
EntityUsernamePasswordaction
+ + + + + +
+
+ + +
+
+
+
+
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index 110d8fa472b..3c867373760 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -388,8 +388,8 @@ angular.module('zeppelinWebApp') } /** push the rest */ - $scope.paragraph.authenticationInfo = data.paragraph.authenticationInfo; $scope.paragraph.aborted = data.paragraph.aborted; + $scope.paragraph.user = data.paragraph.user; $scope.paragraph.dateUpdated = data.paragraph.dateUpdated; $scope.paragraph.dateCreated = data.paragraph.dateCreated; $scope.paragraph.dateFinished = data.paragraph.dateFinished; @@ -970,10 +970,7 @@ angular.module('zeppelinWebApp') } return ''; } - var user = 'anonymous'; - if (pdata.authenticationInfo !== null && !isEmpty(pdata.authenticationInfo.user)) { - user = pdata.authenticationInfo.user; - } + var user = (pdata.user === undefined || pdata.user === null) ? 'anonymous' : pdata.user; var desc = 'Took ' + moment.duration(moment(pdata.dateFinished).diff(moment(pdata.dateStarted))).humanize() + '. Last updated by ' + user + ' at ' + moment(pdata.dateUpdated).format('MMMM DD YYYY, h:mm:ss A') + '.'; diff --git a/zeppelin-web/src/components/navbar/navbar.html b/zeppelin-web/src/components/navbar/navbar.html index 361149e09a3..148d67d3311 100644 --- a/zeppelin-web/src/components/navbar/navbar.html +++ b/zeppelin-web/src/components/navbar/navbar.html @@ -52,6 +52,9 @@
  • Interpreter
  • +
  • + Credential +
  • Configuration
  • diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html index a634fbf1821..8efce04e506 100644 --- a/zeppelin-web/src/index.html +++ b/zeppelin-web/src/index.html @@ -55,6 +55,7 @@ + @@ -144,6 +145,7 @@ + diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java index a07c3fa72e6..fa52957c9ea 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java @@ -362,6 +362,14 @@ public String getNotebookAuthorizationPath() { return getRelativeDir(String.format("%s/notebook-authorization.json", getConfDir())); } + public Boolean credentialsPersist() { + return getBoolean(ConfVars.ZEPPELIN_CREDENTIALS_PERSIST); + } + + public String getCredentialsPath() { + return getRelativeDir(String.format("%s/credentials.json", getConfDir())); + } + public String getShiroPath() { return getRelativeDir(String.format("%s/shiro.ini", getConfDir())); } @@ -528,8 +536,10 @@ public static enum ConfVars { // i.e. http://localhost:8080 ZEPPELIN_ALLOWED_ORIGINS("zeppelin.server.allowed.origins", "*"), ZEPPELIN_ANONYMOUS_ALLOWED("zeppelin.anonymous.allowed", true), + ZEPPELIN_CREDENTIALS_PERSIST("zeppelin.credentials.persist", true), ZEPPELIN_WEBSOCKET_MAX_TEXT_MESSAGE_SIZE("zeppelin.websocket.max.text.message.size", "1024000"); + private String varName; @SuppressWarnings("rawtypes") private Class varClass; diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java index a73aad95a8f..6941dc9380c 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java @@ -40,6 +40,7 @@ import com.google.gson.Gson; import org.apache.zeppelin.user.AuthenticationInfo; +import org.apache.zeppelin.user.Credentials; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -70,6 +71,7 @@ public class Note implements Serializable, JobListener { private transient NotebookRepo repo; private transient SearchService index; private transient ScheduledFuture delayedPersist; + private transient Credentials credentials; /** * note configurations. @@ -89,11 +91,12 @@ public class Note implements Serializable, JobListener { public Note() {} public Note(NotebookRepo repo, NoteInterpreterLoader replLoader, - JobListenerFactory jlFactory, SearchService noteIndex) { + JobListenerFactory jlFactory, SearchService noteIndex, Credentials credentials) { this.repo = repo; this.replLoader = replLoader; this.jobListenerFactory = jlFactory; this.index = noteIndex; + this.credentials = credentials; generateId(); } @@ -145,6 +148,15 @@ public void setIndex(SearchService index) { this.index = index; } + public Credentials getCredentials() { + return credentials; + }; + + public void setCredentials(Credentials credentials) { + this.credentials = credentials; + } + + @SuppressWarnings("rawtypes") public Map> getAngularObjects() { return angularObjects; diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java index 986753c9cc4..4e611119bb8 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java @@ -43,6 +43,7 @@ import org.apache.zeppelin.resource.ResourcePoolUtils; import org.apache.zeppelin.scheduler.SchedulerFactory; import org.apache.zeppelin.search.SearchService; +import org.apache.zeppelin.user.Credentials; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; import org.quartz.JobBuilder; @@ -78,6 +79,7 @@ public class Notebook { private NotebookRepo notebookRepo; private SearchService notebookIndex; private NotebookAuthorization notebookAuthorization; + private Credentials credentials; /** * Main constructor \w manual Dependency Injection @@ -97,7 +99,8 @@ public Notebook(ZeppelinConfiguration conf, NotebookRepo notebookRepo, SchedulerFactory schedulerFactory, InterpreterFactory replFactory, JobListenerFactory jobListenerFactory, SearchService notebookIndex, - NotebookAuthorization notebookAuthorization) throws IOException, SchedulerException { + NotebookAuthorization notebookAuthorization, + Credentials credentials) throws IOException, SchedulerException { this.conf = conf; this.notebookRepo = notebookRepo; this.schedulerFactory = schedulerFactory; @@ -105,6 +108,7 @@ public Notebook(ZeppelinConfiguration conf, NotebookRepo notebookRepo, this.jobListenerFactory = jobListenerFactory; this.notebookIndex = notebookIndex; this.notebookAuthorization = notebookAuthorization; + this.credentials = credentials; quertzSchedFact = new org.quartz.impl.StdSchedulerFactory(); quartzSched = quertzSchedFact.getScheduler(); quartzSched.start(); @@ -146,7 +150,7 @@ public Note createNote() throws IOException { */ public Note createNote(List interpreterIds) throws IOException { NoteInterpreterLoader intpLoader = new NoteInterpreterLoader(replFactory); - Note note = new Note(notebookRepo, intpLoader, jobListenerFactory, notebookIndex); + Note note = new Note(notebookRepo, intpLoader, jobListenerFactory, notebookIndex, credentials); intpLoader.setNoteId(note.id()); synchronized (notes) { notes.put(note.id(), note); @@ -335,6 +339,7 @@ private Note loadNoteFromRepo(String id) { //Manually inject ALL dependencies, as DI constructor was NOT used note.setIndex(this.notebookIndex); + note.setCredentials(this.credentials); NoteInterpreterLoader replLoader = new NoteInterpreterLoader(replFactory); note.setReplLoader(replLoader); 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 212d6089f71..82f6138aea2 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 @@ -49,8 +49,7 @@ public NotebookAuthorization(ZeppelinConfiguration conf) { try { loadFromFile(); } catch (IOException e) { - LOG.error("Error loading NotebookAuthorization"); - e.printStackTrace(); + LOG.error("Error loading NotebookAuthorization", e); } } 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 9a50a1ace39..36d466bbe76 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 @@ -20,6 +20,9 @@ import org.apache.zeppelin.display.AngularObject; import org.apache.zeppelin.display.AngularObjectRegistry; import org.apache.zeppelin.user.AuthenticationInfo; +import org.apache.zeppelin.user.Credentials; +import org.apache.zeppelin.user.UserCredentials; +import org.apache.zeppelin.user.UsernamePassword; import org.apache.zeppelin.display.GUI; import org.apache.zeppelin.display.Input; import org.apache.zeppelin.interpreter.*; @@ -47,10 +50,11 @@ public class Paragraph extends Job implements Serializable, Cloneable { private transient NoteInterpreterLoader replLoader; private transient Note note; + private transient AuthenticationInfo authenticationInfo; String title; String text; - AuthenticationInfo authenticationInfo; + String user; Date dateUpdated; private Map config; // paragraph configs like isOpen, colWidth, etc public final GUI settings; // form and parameter settings @@ -70,6 +74,7 @@ public Paragraph(String paragraphId, Note note, JobListener listener, title = null; text = null; authenticationInfo = null; + user = null; dateUpdated = null; settings = new GUI(); config = new HashMap(); @@ -107,6 +112,7 @@ public AuthenticationInfo getAuthenticationInfo() { public void setAuthenticationInfo(AuthenticationInfo authenticationInfo) { this.authenticationInfo = authenticationInfo; + this.user = authenticationInfo.getUser(); } public String getTitle() { @@ -330,6 +336,14 @@ private InterpreterContext getInterpreterContext() { } final Paragraph self = this; + + Credentials credentials = note.getCredentials(); + if (authenticationInfo != null) { + UserCredentials userCredentials = credentials.getUserCredentials( + authenticationInfo.getUser()); + authenticationInfo.setUserCredentials(userCredentials); + } + InterpreterContext interpreterContext = new InterpreterContext( note.id(), getId(), 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 23a4b1b40b8..80ef50f05ae 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 @@ -46,6 +46,7 @@ import org.apache.zeppelin.scheduler.JobListener; import org.apache.zeppelin.scheduler.SchedulerFactory; import org.apache.zeppelin.search.SearchService; +import org.apache.zeppelin.user.Credentials; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -65,9 +66,12 @@ public class NotebookTest implements JobListenerFactory{ private NotebookRepo notebookRepo; private InterpreterFactory factory; private DependencyResolver depResolver; + private NotebookAuthorization notebookAuthorization; + private Credentials credentials; @Before public void setUp() throws Exception { + tmpDir = new File(System.getProperty("java.io.tmpdir")+"/ZeppelinLTest_"+System.currentTimeMillis()); tmpDir.mkdirs(); new File(tmpDir, "conf").mkdirs(); @@ -90,8 +94,12 @@ public void setUp() throws Exception { SearchService search = mock(SearchService.class); notebookRepo = new VFSNotebookRepo(conf); - NotebookAuthorization notebookAuthorization = new NotebookAuthorization(conf); - notebook = new Notebook(conf, notebookRepo, schedulerFactory, factory, this, search, notebookAuthorization); + notebookAuthorization = new NotebookAuthorization(conf); + credentials = new Credentials(conf.credentialsPersist(), conf.getCredentialsPath()); + + notebook = new Notebook(conf, notebookRepo, schedulerFactory, factory, this, search, + notebookAuthorization, credentials); + } @After @@ -175,7 +183,7 @@ public void testPersist() throws IOException, SchedulerException, RepositoryExce Notebook notebook2 = new Notebook( conf, notebookRepo, schedulerFactory, - new InterpreterFactory(conf, null, null, null, depResolver), this, null, null); + new InterpreterFactory(conf, null, null, null, depResolver), this, null, null, null); assertEquals(1, notebook2.getAllNotes().size()); } 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 69b50b43933..1699d681bdf 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 @@ -42,6 +42,7 @@ import org.apache.zeppelin.scheduler.SchedulerFactory; import org.apache.zeppelin.search.SearchService; import org.apache.zeppelin.search.LuceneSearch; +import org.apache.zeppelin.user.Credentials; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -63,6 +64,7 @@ public class NotebookRepoSyncTest implements JobListenerFactory { private DependencyResolver depResolver; private SearchService search; private NotebookAuthorization notebookAuthorization; + private Credentials credentials; private static final Logger LOG = LoggerFactory.getLogger(NotebookRepoSyncTest.class); @Before @@ -97,7 +99,9 @@ public void setUp() throws Exception { search = mock(SearchService.class); notebookRepoSync = new NotebookRepoSync(conf); notebookAuthorization = new NotebookAuthorization(conf); - notebookSync = new Notebook(conf, notebookRepoSync, schedulerFactory, factory, this, search, notebookAuthorization); + credentials = new Credentials(conf.credentialsPersist(), conf.getCredentialsPath()); + notebookSync = new Notebook(conf, notebookRepoSync, schedulerFactory, factory, this, search, + notebookAuthorization, credentials); } @After @@ -222,7 +226,8 @@ public void testCheckpointOneStorage() throws IOException, SchedulerException { ZeppelinConfiguration vConf = ZeppelinConfiguration.create(); NotebookRepoSync vRepoSync = new NotebookRepoSync(vConf); - Notebook vNotebookSync = new Notebook(vConf, vRepoSync, schedulerFactory, factory, this, search, null); + Notebook vNotebookSync = new Notebook(vConf, vRepoSync, schedulerFactory, factory, this, search, + notebookAuthorization, credentials); // one git versioned storage initialized assertThat(vRepoSync.getRepoCount()).isEqualTo(1); diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepoTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepoTest.java index 6058f821825..e5915a97299 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepoTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepoTest.java @@ -80,7 +80,7 @@ public void setUp() throws Exception { SearchService search = mock(SearchService.class); notebookRepo = new VFSNotebookRepo(conf); - notebook = new Notebook(conf, notebookRepo, schedulerFactory, factory, this, search, null); + notebook = new Notebook(conf, notebookRepo, schedulerFactory, factory, this, search, null, null); } @After diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/search/LuceneSearchTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/search/LuceneSearchTest.java index c744267ec22..dcb68c82dc8 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/search/LuceneSearchTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/search/LuceneSearchTest.java @@ -286,7 +286,7 @@ private Paragraph addParagraphWithTextAndTitle(Note note, String text, String ti } private Note newNote(String name) { - Note note = new Note(notebookRepoMock, replLoaderMock, null, notebookIndex); + Note note = new Note(notebookRepoMock, replLoaderMock, null, notebookIndex, null); note.setName(name); return note; }