You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by bz...@apache.org on 2016/06/30 08:19:28 UTC

zeppelin git commit: [ZEPPELIN-1023] Add more credential apis.

Repository: zeppelin
Updated Branches:
  refs/heads/master 3a338e01e -> c348161df


[ZEPPELIN-1023] Add more credential apis.

### What is this PR for?
This PR is for supporting various Credential APIs for users.

### What type of PR is it?
Improvement

### What is the Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-1023

### How should this be tested?
  - to create credential information.
```
curl -XPUT -H "Content-Type: application/json" "http://localhost:8080/api/credential" -d '{"entity" : "e1", "username" : "user1", "password" : "testpass"}'
```

  - to get credential information.
```
curl -XGET -H "Content-Type: application/json" "http://localhost:8080/api/credential"
```

  - to remove credential entity information.
```
curl -XDELETE -H "Content-Type: application/json" "http://localhost:8080/api/credential/e1"
```

  - to remove all credential information.
```
curl -XDELETE -H "Content-Type: application/json" "http://localhost:8080/api/credential"
```

### Questions:
* Does the licenses files need update? no
* Is there breaking changes for older versions? no
* Does this needs documentation? no

Author: astroshim <hs...@nflabs.com>
Author: AhyoungRyu <fb...@hanmail.net>
Author: HyungSung <hs...@nflabs.com>

Closes #1030 from astroshim/ZEPPELIN-1023 and squashes the following commits:

1b94ae1 [astroshim] Merge branch 'master' into ZEPPELIN-1023
a580476 [astroshim] erase comment code
0609931 [astroshim] update credentialsMap
0f503fd [astroshim] to fix ci
d0c2bac [astroshim] changed syncronizedmap to concurrenthashmap
7b2a7c5 [HyungSung] Merge pull request #7 from AhyoungRyu/ZEPPELIN-1023-docs
80d8a30 [AhyoungRyu] Add rest-credential.md to index.md & dropdown menu
f546532 [astroshim] add rest-credential document
26433f2 [astroshim] change using syncronizedMap.
9b2c1c9 [astroshim] add checking null and blank values in the putCredentials method.
0371701 [astroshim] add more credential apis.


Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/c348161d
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/c348161d
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/c348161d

Branch: refs/heads/master
Commit: c348161df00d5db72943f96527ac52d0e5419751
Parents: 3a338e0
Author: astroshim <hs...@nflabs.com>
Authored: Mon Jun 27 22:38:47 2016 +0900
Committer: Alexander Bezzubov <bz...@apache.org>
Committed: Thu Jun 30 17:19:20 2016 +0900

----------------------------------------------------------------------
 docs/_includes/themes/zeppelin/_navigation.html |   1 +
 docs/index.md                                   |   1 +
 docs/rest-api/rest-credential.md                | 181 +++++++++++++++++++
 .../org/apache/zeppelin/user/Credentials.java   |  25 ++-
 .../apache/zeppelin/user/UserCredentials.java   |  16 +-
 .../apache/zeppelin/rest/CredentialRestApi.java |  66 ++++++-
 .../zeppelin/rest/CredentialsRestApiTest.java   |  60 +++++-
 7 files changed, 334 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c348161d/docs/_includes/themes/zeppelin/_navigation.html
----------------------------------------------------------------------
diff --git a/docs/_includes/themes/zeppelin/_navigation.html b/docs/_includes/themes/zeppelin/_navigation.html
index b5703af..fce8cee 100644
--- a/docs/_includes/themes/zeppelin/_navigation.html
+++ b/docs/_includes/themes/zeppelin/_navigation.html
@@ -94,6 +94,7 @@
                 <li><a href="{{BASE_PATH}}/rest-api/rest-interpreter.html">Interpreter API</a></li>
                 <li><a href="{{BASE_PATH}}/rest-api/rest-notebook.html">Notebook API</a></li>
                 <li><a href="{{BASE_PATH}}/rest-api/rest-configuration.html">Configuration API</a></li>
+                <li><a href="{{BASE_PATH}}/rest-api/rest-credential.html">Credential API</a></li>
                 <li role="separator" class="divider"></li>
                 <li class="title"><span><b>Security</b><span></li>
                 <li><a href="{{BASE_PATH}}/security/authentication.html">Authentication for NGINX</a></li>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c348161d/docs/index.md
----------------------------------------------------------------------
diff --git a/docs/index.md b/docs/index.md
index 9847aa4..10fc7e1 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -162,6 +162,7 @@ Join to our [Mailing list](https://zeppelin.apache.org/community.html) and repor
   * [Interpreter API](./rest-api/rest-interpreter.html)
   * [Notebook API](./rest-api/rest-notebook.html)
   * [Configuration API](./rest-api/rest-configuration.html)
+  * [Credential API](./rest-api/rest-credential.html)
 * Security: available security support in Apache Zeppelin
   * [Authentication for NGINX](./security/authentication.html)
   * [Shiro Authentication](./security/shiroauthentication.html)

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c348161d/docs/rest-api/rest-credential.md
----------------------------------------------------------------------
diff --git a/docs/rest-api/rest-credential.md b/docs/rest-api/rest-credential.md
new file mode 100644
index 0000000..a01f335
--- /dev/null
+++ b/docs/rest-api/rest-credential.md
@@ -0,0 +1,181 @@
+---
+layout: page
+title: "Credentials REST API"
+description: ""
+group: rest-api
+---
+<!--
+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.
+-->
+{% include JB/setup %}
+
+## Zeppelin REST API
+ Zeppelin provides several REST APIs for interaction and remote activation of zeppelin functionality.
+
+ All REST APIs are available starting with the following endpoint `http://[zeppelin-server]:[zeppelin-port]/api`. Note that zeppelin REST APIs receive or return JSON objects, it is recommended for you to install some JSON viewers such as [JSONView](https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc).
+
+ If you work with Zeppelin and find a need for an additional REST API, please [file an issue or send us mail](http://zeppelin.apache.org/community.html).
+
+ <br />
+## Credential REST API List
+
+### List Credential information
+  <table class="table-credential">
+    <col width="200">
+    <tr>
+      <td>Description</td>
+      <td>This ```GET``` method returns all key/value pairs of credential information on the server.</td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/credential```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td> Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td> sample JSON response
+      </td>
+      <td>
+        <pre>
+{
+  "status": "OK",
+  "message": "",
+  "body": {
+    "userCredentials":{
+      "entity1":{
+        "username":"user1",
+        "password":"password1"
+      },
+      "entity2":{
+        "username":"user2",
+        "password":"password2"
+      }
+    }
+  }
+}</pre></td>
+    </tr>
+  </table>
+
+<br/>
+### Create an Credential Information
+  <table class="table-credential">
+    <col width="200">
+    <tr>
+      <td>Description</td>
+      <td>This ```PUT``` method creates an credential information with new properties.</td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/credential/```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td>Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td>Sample JSON input</td>
+      <td>
+        <pre>
+{
+  "entity": "e1",
+  "username": "user",
+  "password": "password"
+}
+        </pre>
+      </td>
+    </tr>
+    <tr>
+      <td>Sample JSON response</td>
+      <td>
+        <pre>
+{
+  "status": "OK"
+}
+        </pre>
+      </td>
+    </tr>
+  </table>
+
+
+<br/>
+### Delete all Credential Information
+
+  <table class="table-credential">
+    <col width="200">
+    <tr>
+      <td>Description</td>
+      <td>This ```DELETE``` method deletes credential information.</td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/credential```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td> Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td>Sample JSON response</td>
+      <td>
+        <code>{"status":"OK"}</code>
+      </td>
+    </tr>
+  </table>
+
+
+<br/>
+### Delete an Credential entity
+
+  <table class="table-credential">
+    <col width="200">
+    <tr>
+      <td>Description</td>
+      <td>This ```DELETE``` method deletes an given credential entity.</td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/credential/[entity]```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td> Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td>Sample JSON response</td>
+      <td>
+        <code>{"status":"OK"}</code>
+      </td>
+    </tr>
+  </table>
+
+
+<br/>
+

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c348161d/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/Credentials.java
----------------------------------------------------------------------
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
index b3a3726..72d44e9 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/Credentials.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/Credentials.java
@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
 import java.io.*;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Class defining credentials for data source authorization
@@ -44,6 +45,7 @@ public class Credentials {
       credentialsFile = new File(credentialsPath);
     }
     credentialsMap = new HashMap<>();
+
     if (credentialsPersist) {
       GsonBuilder builder = new GsonBuilder();
       builder.setPrettyPrinting();
@@ -62,6 +64,28 @@ public class Credentials {
 
   public void putUserCredentials(String username, UserCredentials uc) throws IOException {
     credentialsMap.put(username, uc);
+    saveCredentials();
+  }
+
+  public UserCredentials removeUserCredentials(String username) throws IOException {
+    UserCredentials uc;
+    uc = credentialsMap.remove(username);
+    saveCredentials();
+    return uc;
+  }
+
+  public boolean removeCredentialEntity(String username, String entity) throws IOException {
+    UserCredentials uc = credentialsMap.get(username);
+    if (uc != null && uc.existUsernamePassword(entity) == false) {
+      return false;
+    }
+
+    uc.removeUsernamePassword(entity);
+    saveCredentials();
+    return true;
+  }
+
+  public void saveCredentials() throws IOException {
     if (credentialsPersist) {
       saveToFile();
     }
@@ -118,5 +142,4 @@ public class Credentials {
       LOG.error("Error saving credentials file", e);
     }
   }
-
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c348161d/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/UserCredentials.java
----------------------------------------------------------------------
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
index 166840a..f952866 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/UserCredentials.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/UserCredentials.java
@@ -17,18 +17,14 @@
 
 package org.apache.zeppelin.user;
 
-import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * User Credentials POJO
  */
 public class UserCredentials {
-  private Map<String, UsernamePassword> userCredentials;
-
-  public UserCredentials() {
-    this.userCredentials = new HashMap<>();
-  }
+  private Map<String, UsernamePassword> userCredentials = new ConcurrentHashMap<>();
 
   public UsernamePassword getUsernamePassword(String entity) {
     return userCredentials.get(entity);
@@ -38,6 +34,14 @@ public class UserCredentials {
     userCredentials.put(entity, up);
   }
 
+  public void removeUsernamePassword(String entity) {
+    userCredentials.remove(entity);
+  }
+
+  public boolean existUsernamePassword(String entity) {
+    return userCredentials.containsKey(entity);
+  }
+
   @Override
   public String toString() {
     return "UserCredentials{" +

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c348161d/zeppelin-server/src/main/java/org/apache/zeppelin/rest/CredentialRestApi.java
----------------------------------------------------------------------
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
old mode 100644
new mode 100755
index 6904a32..74412c4
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/CredentialRestApi.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/CredentialRestApi.java
@@ -17,6 +17,7 @@
 
 package org.apache.zeppelin.rest;
 
+import com.google.common.base.Strings;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import org.apache.zeppelin.user.Credentials;
@@ -50,7 +51,6 @@ public class CredentialRestApi {
   private HttpServletRequest servReq;
 
   public CredentialRestApi() {
-
   }
 
   public CredentialRestApi(Credentials credentials) {
@@ -58,18 +58,22 @@ public class CredentialRestApi {
   }
 
   /**
-   * Update credentials for current user
+   * Put User Credentials REST API
+   * @param message - JSON with entity, username, password.
+   * @return JSON with status.OK
+   * @throws IOException, IllegalArgumentException
    */
   @PUT
-  public Response putCredentials(String message) throws IOException {
+  public Response putCredentials(String message) throws IOException, IllegalArgumentException {
     Map<String, String> messageMap = gson.fromJson(message,
       new TypeToken<Map<String, String>>(){}.getType());
     String entity = messageMap.get("entity");
     String username = messageMap.get("username");
     String password = messageMap.get("password");
 
-    if (entity == null || username == null || password == null) {
-      return new JsonResponse(Status.BAD_REQUEST, "", "").build();
+    if (Strings.isNullOrEmpty(entity)
+            || Strings.isNullOrEmpty(username) || Strings.isNullOrEmpty(password) ) {
+      return new JsonResponse(Status.BAD_REQUEST).build();
     }
 
     String user = SecurityUtils.getPrincipal();
@@ -77,7 +81,57 @@ public class CredentialRestApi {
     UserCredentials uc = credentials.getUserCredentials(user);
     uc.putUsernamePassword(entity, new UsernamePassword(username, password));
     credentials.putUserCredentials(user, uc);
-    return new JsonResponse(Status.OK, "", "").build();
+    return new JsonResponse(Status.OK).build();
+  }
+
+  /**
+   * Get User Credentials list REST API
+   * @param
+   * @return JSON with status.OK
+   * @throws IOException, IllegalArgumentException
+   */
+  @GET
+  public Response getCredentials(String message) throws
+      IOException, IllegalArgumentException {
+    String user = SecurityUtils.getPrincipal();
+    logger.info("getCredentials credentials for user {} ", user);
+    UserCredentials uc = credentials.getUserCredentials(user);
+    return new JsonResponse(Status.OK, uc).build();
+  }
+
+ /**
+  * Remove User Credentials REST API
+  * @param
+  * @return JSON with status.OK
+  * @throws IOException, IllegalArgumentException
+  */
+  @DELETE
+  public Response removeCredentials(String message) throws
+      IOException, IllegalArgumentException {
+    String user = SecurityUtils.getPrincipal();
+    logger.info("removeCredentials credentials for user {} ", user);
+    UserCredentials uc = credentials.removeUserCredentials(user);
+    if (uc == null) {
+      return new JsonResponse(Status.NOT_FOUND).build();
+    }
+    return new JsonResponse(Status.OK).build();
   }
 
+  /**
+   * Remove Entity of User Credential entity REST API
+   * @param
+   * @return JSON with status.OK
+   * @throws IOException, IllegalArgumentException
+   */
+  @DELETE
+  @Path("{entity}")
+  public Response removeCredentialEntity(@PathParam("entity") String entity) throws
+          IOException, IllegalArgumentException {
+    String user = SecurityUtils.getPrincipal();
+    logger.info("removeCredentialEntity for user {} entity {}", user, entity);
+    if (credentials.removeCredentialEntity(user, entity) == false) {
+      return new JsonResponse(Status.NOT_FOUND).build();
+    }
+    return new JsonResponse(Status.OK).build();
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c348161d/zeppelin-server/src/test/java/org/apache/zeppelin/rest/CredentialsRestApiTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/CredentialsRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/CredentialsRestApiTest.java
index 674c47e..29c2914 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/CredentialsRestApiTest.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/CredentialsRestApiTest.java
@@ -19,19 +19,26 @@ package org.apache.zeppelin.rest;
 
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
+import org.apache.commons.httpclient.methods.DeleteMethod;
 import org.apache.commons.httpclient.methods.GetMethod;
 import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.zeppelin.notebook.Note;
+import org.apache.zeppelin.server.ZeppelinServer;
+import org.apache.zeppelin.user.UserCredentials;
+import org.apache.zeppelin.utils.SecurityUtils;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.Map;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
+import static org.junit.Assert.*;
 
 public class CredentialsRestApiTest extends AbstractTestRestApi {
+  protected static final Logger LOG = LoggerFactory.getLogger(CredentialsRestApiTest.class);
   Gson gson = new Gson();
 
   @BeforeClass
@@ -72,5 +79,52 @@ public class CredentialsRestApiTest extends AbstractTestRestApi {
     allNullPut.releaseConnection();
   }
 
-}
+  public Map<String, UserCredentials> testGetUserCredentials() throws IOException {
+    GetMethod getMethod = httpGet("/credential");
+    getMethod.addRequestHeader("Origin", "http://localhost");
+    Map<String, Object> resp = gson.fromJson(getMethod.getResponseBodyAsString(),
+            new TypeToken<Map<String, Object>>(){}.getType());
+    Map<String, Object> body = (Map<String, Object>) resp.get("body");
+    Map<String, UserCredentials> credentialMap = (Map<String, UserCredentials>)body.get("userCredentials");
+    getMethod.releaseConnection();
+    return credentialMap;
+  }
+
+  public void testPutUserCredentials(String requestData) throws IOException {
+    PutMethod putMethod = httpPut("/credential", requestData);
+    putMethod.addRequestHeader("Origin", "http://localhost");
+    assertThat(putMethod, isAllowed());
+    putMethod.releaseConnection();
+  }
+
+  public void testRemoveUserCredentials() throws IOException {
+    DeleteMethod deleteMethod = httpDelete("/credential/");
+    assertThat("Test delete method:", deleteMethod, isAllowed());
+    deleteMethod.releaseConnection();
+  }
+
+  public void testRemoveCredentialEntity(String entity) throws IOException {
+    DeleteMethod deleteMethod = httpDelete("/credential/" + entity);
+    assertThat("Test delete method:", deleteMethod, isAllowed());
+    deleteMethod.releaseConnection();
+  }
+
+  @Test
+  public void testCredentialsAPIs() throws IOException {
+    String requestData1 = "{\"entity\" : \"entityname\", \"username\" : \"myuser\", \"password\" : \"mypass\"}";
+    String entity = "entityname";
+    Map<String, UserCredentials> credentialMap;
+
+    testPutUserCredentials(requestData1);
+    credentialMap = testGetUserCredentials();
+    assertNotNull("CredentialMap should be null", credentialMap);
 
+    testRemoveCredentialEntity(entity);
+    credentialMap = testGetUserCredentials();
+    assertNull("CredentialMap should be null", credentialMap.get("entity1"));
+
+    testRemoveUserCredentials();
+    credentialMap = testGetUserCredentials();
+    assertEquals("Compare CredentialMap", credentialMap.toString(), "{}");
+  }
+}