Repository: zeppelin
Updated Branches:
  refs/heads/branch-0.6 c1bddddef -> 9c5ae39cc


ZEPPELIN-1164: backport new Shiro realm to 0.6 branch

Just a backport of #1173

Author: Anthony Corbacho <[email protected]>

Closes #1344 from bzz/branch-0.6-add-realm and squashes the following commits:

fbf8615 [Anthony Corbacho] [ZEPPELIN-1164] ZeppelinHub Realm


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

Branch: refs/heads/branch-0.6
Commit: 9c5ae39ccc154beac92c08c6a82e032e153e80d1
Parents: c1bdddd
Author: Anthony Corbacho <[email protected]>
Authored: Fri Jul 22 15:55:50 2016 +0900
Committer: Alexander Bezzubov <[email protected]>
Committed: Fri Aug 19 18:21:58 2016 +0900

----------------------------------------------------------------------
 conf/shiro.ini                                  |   5 +
 docs/security/shiroauthentication.md            |  47 ++++-
 .../apache/zeppelin/realm/ZeppelinHubRealm.java | 199 +++++++++++++++++++
 3 files changed, 249 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9c5ae39c/conf/shiro.ini
----------------------------------------------------------------------
diff --git a/conf/shiro.ini b/conf/shiro.ini
index ced9776..e774624 100644
--- a/conf/shiro.ini
+++ b/conf/shiro.ini
@@ -42,6 +42,11 @@ user3 = password4, role2
 #ldapRealm.userDnTemplate = uid={0},ou=Users,dc=COMPANY,dc=COM
 #ldapRealm.contextFactory.authenticationMechanism = SIMPLE
 
+### A sample for configuring ZeppelinHub Realm
+#zeppelinHubRealm = org.apache.zeppelin.realm.ZeppelinHubRealm
+## Url of ZeppelinHub
+#zeppelinHubRealm.zeppelinhubUrl = https://www.zeppelinhub.com
+#securityManager.realms = $zeppelinHubRealm
 
 sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9c5ae39c/docs/security/shiroauthentication.md
----------------------------------------------------------------------
diff --git a/docs/security/shiroauthentication.md 
b/docs/security/shiroauthentication.md
index 733ff11..f698a0a 100644
--- a/docs/security/shiroauthentication.md
+++ b/docs/security/shiroauthentication.md
@@ -101,6 +101,49 @@ finance = *
 group1 = *
 ```
 
-All of above configurations are defined in the `conf/shiro.ini` file.
+## Configure Realm (optional)
+Realms are responsible for authentication and authorization in Apache 
Zeppelin. By default, Apache Zeppelin uses 
[IniRealm](https://shiro.apache.org/static/latest/apidocs/org/apache/shiro/realm/text/IniRealm.html)
 (users and groups are configurable in `conf/shiro.ini` file under `[user]` and 
`[group]` section). You can also leverage Shiro Realms like 
[JndiLdapRealm](https://shiro.apache.org/static/latest/apidocs/org/apache/shiro/realm/ldap/JndiLdapRealm.html),
 
[JdbcRealm](https://shiro.apache.org/static/latest/apidocs/org/apache/shiro/realm/jdbc/JdbcRealm.html)
 or create [our 
own](https://shiro.apache.org/static/latest/apidocs/org/apache/shiro/realm/AuthorizingRealm.html).
+To learn more about Apache Shiro Realm, please check [this 
documentation](http://shiro.apache.org/realm.html).
+
+We also provide community custom Realms.
+
+### Active Directory
+TBD
+
+### LDAP
+TBD
+
+### ZeppelinHub
+[ZeppelinHub](https://www.zeppelinhub.com) is a service that synchronize your 
Apache Zeppelin notebooks and enables you to collaborate easily.
+
+To enable login with your ZeppelinHub credential, apply the following change 
in `conf/shiro.ini` under `[main]` section.
+
+```
+### A sample for configuring ZeppelinHub Realm
+zeppelinHubRealm = org.apache.zeppelin.realm.ZeppelinHubRealm
+## Url of ZeppelinHub
+zeppelinHubRealm.zeppelinhubUrl = https://www.zeppelinhub.com
+securityManager.realms = $zeppelinHubRealm
+```
+
+> Note: ZeppelinHub is not releated to apache Zeppelin project.
+
+## Secure your Zeppelin information (optional)
+By default, anyone who defined in `[users]` can share **Interpreter Setting**, 
**Credential** and **Configuration** information in Apache Zeppelin. 
+Sometimes you might want to hide these information for your use case. 
+Since Shiro provides **url-based security**, you can hide the information by 
commenting or uncommenting these below lines in `conf/shiro.ini`.
+
+```
+[urls]
+
+/api/interpreter/** = authc, roles[admin]
+/api/configurations/** = authc, roles[admin]
+/api/credential/** = authc, roles[admin]
+```
+
+In this case, only who have `admin` role can see **Interpreter Setting**, 
**Credential** and **Configuration** information. 
+If you want to grant this permission to other users, you can change **roles[ 
]** as you defined at `[users]` section.
+
+<br/>
+> **NOTE :** All of the above configurations are defined in the 
`conf/shiro.ini` file. This documentation is originally from 
[SECURITY-README.md](https://github.com/apache/zeppelin/blob/master/SECURITY-README.md).
 
-> **NOTE :** This documentation is originally from 
[SECURITY-README.md](https://github.com/apache/zeppelin/blob/master/SECURITY-README.md).

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9c5ae39c/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ZeppelinHubRealm.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ZeppelinHubRealm.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ZeppelinHubRealm.java
new file mode 100644
index 0000000..cbe490d
--- /dev/null
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ZeppelinHubRealm.java
@@ -0,0 +1,199 @@
+/*
+ * 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.realm;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.commons.httpclient.methods.StringRequestEntity;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shiro.authc.AccountException;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Joiner;
+import com.google.gson.Gson;
+import com.google.gson.JsonParseException;
+
+/**
+ * A {@code Realm} implementation that uses the ZeppelinHub to authenticate 
users.
+ *
+ */
+public class ZeppelinHubRealm extends AuthorizingRealm {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(ZeppelinHubRealm.class);
+  private static final String DEFAULT_ZEPPELINHUB_URL = 
"https://www.zeppelinhub.com";;
+  private static final String USER_LOGIN_API_ENDPOINT = "api/v1/users/login";
+  private static final String JSON_CONTENT_TYPE = "application/json";
+  private static final String UTF_8_ENCODING = "UTF-8";
+  private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger();
+
+  private final HttpClient httpClient;
+  private final Gson gson;
+
+  private String zeppelinhubUrl;
+  private String name;
+
+  public ZeppelinHubRealm() {
+    super();
+    LOG.debug("Init ZeppelinhubRealm");
+    //TODO(anthonyc): think about more setting for this HTTP client.
+    //                eg: if user uses proxy etcetc...
+    httpClient = new HttpClient();
+    gson = new Gson();
+    name = getClass().getName() + "_" + INSTANCE_COUNT.getAndIncrement();
+  }
+
+  @Override
+  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken 
authToken)
+      throws AuthenticationException {
+    UsernamePasswordToken token = (UsernamePasswordToken) authToken;
+    if (StringUtils.isBlank(token.getUsername())) {
+      throw new AccountException("Empty usernames are not allowed by this 
realm.");
+    }
+    String loginPayload = createLoginPayload(token.getUsername(), 
token.getPassword());
+    User user = authenticateUser(loginPayload);
+    LOG.debug("{} successfully login via ZeppelinHub", user.login);
+    return new SimpleAuthenticationInfo(user.login, token.getPassword(), name);
+  }
+  
+  @Override
+  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection 
principals) {
+    // TODO(xxx): future work will be done here.
+    return null;
+  }
+  
+  protected void onInit() {
+    super.onInit();
+  }
+  
+  /**
+   * Setter of ZeppelinHub URL, this will be called by Shiro based on 
zeppelinhubUrl property
+   * in shiro.ini file.</p>
+   * It will also perform a check of ZeppelinHub url {@link 
#isZeppelinHubUrlValid}, 
+   * if the url is not valid, the default zeppelinhub url will be used.
+   * 
+   * @param url
+   */
+  public void setZeppelinhubUrl(String url) {
+    if (StringUtils.isBlank(url)) {
+      LOG.warn("Zeppelinhub url is empty, setting up default url {}", 
DEFAULT_ZEPPELINHUB_URL);
+      zeppelinhubUrl = DEFAULT_ZEPPELINHUB_URL;
+    } else {
+      zeppelinhubUrl = (isZeppelinHubUrlValid(url) ? url : 
DEFAULT_ZEPPELINHUB_URL);
+      LOG.info("Setting up Zeppelinhub url to {}", zeppelinhubUrl);
+    }
+  }
+
+  /**
+   * Send to ZeppelinHub a login request based on the request body which is a 
JSON that contains 2 
+   * fields "login" and "password".
+   * 
+   * @param requestBody JSON string of ZeppelinHub payload.
+   * @return Account object with login, name (if set in ZeppelinHub), and mail.
+   * @throws AuthenticationException if fail to login.
+   */
+  protected User authenticateUser(String requestBody) {
+    PutMethod put = new PutMethod(Joiner.on("/").join(zeppelinhubUrl, 
USER_LOGIN_API_ENDPOINT));
+    String responseBody = StringUtils.EMPTY;
+    try {
+      put.setRequestEntity(new StringRequestEntity(requestBody, 
JSON_CONTENT_TYPE, UTF_8_ENCODING));
+      int statusCode = httpClient.executeMethod(put);
+      if (statusCode != HttpStatus.SC_OK) {
+        LOG.error("Cannot login user, HTTP status code is {} instead on 200 
(OK)", statusCode);
+        put.releaseConnection();
+        throw new AuthenticationException("Couldnt login to ZeppelinHub. "
+            + "Login or password incorrect");
+      }
+      responseBody = put.getResponseBodyAsString();
+      put.releaseConnection();
+    } catch (IOException e) {
+      LOG.error("Cannot login user", e);
+      throw new AuthenticationException(e.getMessage());
+    }
+    
+    User account = null;
+    try {
+      account = gson.fromJson(responseBody, User.class);
+    } catch (JsonParseException e) {
+      LOG.error("Cannot deserialize ZeppelinHub response to User instance", e);
+      throw new AuthenticationException("Cannot login to ZeppelinHub");
+    }
+    return account;
+  }
+
+  /**
+   * Create a JSON String that represent login payload.</p>
+   * Payload will look like:
+   * <code>
+   *  {
+   *   'login': 'userLogin',
+   *   'password': 'userpassword'
+   *  }
+   * </code>
+   * @param login
+   * @param pwd
+   * @return
+   */
+  protected String createLoginPayload(String login, char[] pwd) {
+    StringBuilder sb = new StringBuilder("{\"login\":\"");
+    return sb.append(login).append("\", 
\"password\":\"").append(pwd).append("\"}").toString();
+  }
+
+  /**
+   * Perform a Simple URL check by using <code>URI(url).toURL()</code>.
+   * If the url is not valid, the try-catch condition will catch the 
exceptions and return false,
+   * otherwise true will be returned.
+   * 
+   * @param url
+   * @return
+   */
+  protected boolean isZeppelinHubUrlValid(String url) {
+    boolean valid;
+    try {
+      new URI(url).toURL();
+      valid = true;
+    } catch (URISyntaxException | MalformedURLException e) {
+      LOG.error("Zeppelinhub url is not valid, default ZeppelinHub url will be 
used.", e);
+      valid = false;
+    }
+    return valid;
+  }
+
+  /**
+   * Helper class that will be use to deserialize ZeppelinHub response.
+   */
+  protected class User {
+    public String login;
+    public String email;
+    public String name;
+  }
+}

Reply via email to