Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions conf/shiro.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
28 changes: 28 additions & 0 deletions docs/security/shiroauthentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,33 @@ finance = *
group1 = *
```

## 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.

```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any particular reason to remove this sentence?

"All of the above configurations are defined in the conf/shiro.ini file."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDK its after doing my rebase. should i bring it back?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it exists in master, so may need to bring it back. @AhyoungRyu as original author may check it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pushed the fix, @AhyoungRyu sorry about that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anthonycorbacho It's fine :) @khalidhuseynov thanks for your explanation!

### 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.
Expand All @@ -123,3 +150,4 @@ If you want to grant this permission to other users, you can change **roles[ ]**

<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).

Original file line number Diff line number Diff line change
@@ -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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one question, why we have INSTANCE_COUNT here and how name is used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its a shiro specific needs for unique name.

}

@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;
}
}