diff --git a/docs/_includes/themes/zeppelin/_navigation.html b/docs/_includes/themes/zeppelin/_navigation.html index 498a2d36782..1c5a4c26a74 100644 --- a/docs/_includes/themes/zeppelin/_navigation.html +++ b/docs/_includes/themes/zeppelin/_navigation.html @@ -94,6 +94,7 @@
  • Interpreter API
  • Notebook API
  • Configuration API
  • +
  • Credential API
  • Security
  • Authentication for NGINX
  • diff --git a/docs/index.md b/docs/index.md index 3053f428443..8e6c2ca7fc5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -163,6 +163,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) diff --git a/docs/rest-api/rest-credential.md b/docs/rest-api/rest-credential.md new file mode 100644 index 00000000000..a01f3351b90 --- /dev/null +++ b/docs/rest-api/rest-credential.md @@ -0,0 +1,181 @@ +--- +layout: page +title: "Credentials REST API" +description: "" +group: rest-api +--- + +{% 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). + +
    +## Credential REST API List + +### List Credential information + + + + + + + + + + + + + + + + + + + + + + +
    DescriptionThis ```GET``` method returns all key/value pairs of credential information on the server.
    URL```http://[zeppelin-server]:[zeppelin-port]/api/credential```
    Success code200
    Fail code 500
    sample JSON response + +
    +{
    +  "status": "OK",
    +  "message": "",
    +  "body": {
    +    "userCredentials":{
    +      "entity1":{
    +        "username":"user1",
    +        "password":"password1"
    +      },
    +      "entity2":{
    +        "username":"user2",
    +        "password":"password2"
    +      }
    +    }
    +  }
    +}
    + +
    +### Create an Credential Information + + + + + + + + + + + + + + + + + + + + + + + + + + +
    DescriptionThis ```PUT``` method creates an credential information with new properties.
    URL```http://[zeppelin-server]:[zeppelin-port]/api/credential/```
    Success code200
    Fail code 500
    Sample JSON input +
    +{
    +  "entity": "e1",
    +  "username": "user",
    +  "password": "password"
    +}
    +        
    +
    Sample JSON response +
    +{
    +  "status": "OK"
    +}
    +        
    +
    + + +
    +### Delete all Credential Information + + + + + + + + + + + + + + + + + + + + + + + +
    DescriptionThis ```DELETE``` method deletes credential information.
    URL```http://[zeppelin-server]:[zeppelin-port]/api/credential```
    Success code200
    Fail code 500
    Sample JSON response + {"status":"OK"} +
    + + +
    +### Delete an Credential entity + + + + + + + + + + + + + + + + + + + + + + + +
    DescriptionThis ```DELETE``` method deletes an given credential entity.
    URL```http://[zeppelin-server]:[zeppelin-port]/api/credential/[entity]```
    Success code200
    Fail code 500
    Sample JSON response + {"status":"OK"} +
    + + +
    + 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 b3a3726f845..72d44e9e117 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 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 Credentials(Boolean credentialsPersist, String credentialsPath) { credentialsFile = new File(credentialsPath); } credentialsMap = new HashMap<>(); + if (credentialsPersist) { GsonBuilder builder = new GsonBuilder(); builder.setPrettyPrinting(); @@ -62,6 +64,28 @@ public UserCredentials getUserCredentials(String username) { 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 @@ private void saveToFile() throws IOException { LOG.error("Error saving credentials file", e); } } - } 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 166840a68ef..f9528668aa3 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 userCredentials; - - public UserCredentials() { - this.userCredentials = new HashMap<>(); - } + private Map userCredentials = new ConcurrentHashMap<>(); public UsernamePassword getUsernamePassword(String entity) { return userCredentials.get(entity); @@ -38,6 +34,14 @@ public void putUsernamePassword(String entity, UsernamePassword up) { 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{" + 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 6904a32a28b..74412c44862 --- 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 CredentialRestApi(Credentials credentials) { } /** - * 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 messageMap = gson.fromJson(message, new TypeToken>(){}.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 Response putCredentials(String message) throws IOException { 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(); + } } 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 674c47e5f7c..29c2914991d 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 @@ 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 void testInvalidRequest() throws IOException { allNullPut.releaseConnection(); } -} + public Map testGetUserCredentials() throws IOException { + GetMethod getMethod = httpGet("/credential"); + getMethod.addRequestHeader("Origin", "http://localhost"); + Map resp = gson.fromJson(getMethod.getResponseBodyAsString(), + new TypeToken>(){}.getType()); + Map body = (Map) resp.get("body"); + Map credentialMap = (Map)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 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(), "{}"); + } +}