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 <[email protected]> Author: AhyoungRyu <[email protected]> Author: HyungSung <[email protected]> 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 <[email protected]> Authored: Mon Jun 27 22:38:47 2016 +0900 Committer: Alexander Bezzubov <[email protected]> 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(), "{}"); + } +}
