org.codelibs.elasticsearch.auth.service.AuthService Maven / Gradle / Ivy
package org.codelibs.elasticsearch.auth.service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import org.apache.commons.codec.digest.DigestUtils;
import org.codelibs.elasticsearch.auth.AuthException;
import org.codelibs.elasticsearch.auth.filter.ContentFilter;
import org.codelibs.elasticsearch.auth.filter.LoginFilter;
import org.codelibs.elasticsearch.auth.filter.LogoutFilter;
import org.codelibs.elasticsearch.auth.security.Authenticator;
import org.codelibs.elasticsearch.auth.security.LoginConstraint;
import org.codelibs.elasticsearch.auth.util.MapUtil;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.admin.indices.refresh.RefreshResponse;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.netty.handler.codec.http.Cookie;
import org.elasticsearch.common.netty.handler.codec.http.CookieDecoder;
import org.elasticsearch.common.netty.handler.codec.http.HttpHeaders;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestRequest.Method;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
public class AuthService extends AbstractLifecycleComponent {
private static final String DEFAULT_CONSTRAINT_TYPE = "constraint";
private static final String DEFAULT_CONSTRAINT_INDEX_NAME = "security";
private static final String DEFAULT_COOKIE_TOKEN_NAME = "eaid";
private static final String DEFAULT_GUEST_ROLE = "guest";
private RestController restController;
private Map authenticatorMap = new LinkedHashMap();
private Client client;
private String constraintIndex;
private String constraintType;
private String authTokenIndex = "auth";
private String tokenType = "token";
private String tokenKey = "token";
private String guestRole;
private ContentFilter contentFilter;
private boolean cookieToken = true;
private String cookieTokenName;
private boolean updateToken;
@Inject
public AuthService(final Settings settings, final Client client,
final RestController restController) {
super(settings);
this.client = client;
this.restController = restController;
logger.info("Creating authenticators.");
constraintIndex = settings.get("auth.constraint.index",
DEFAULT_CONSTRAINT_INDEX_NAME);
constraintType = settings.get("auth.constraint.type",
DEFAULT_CONSTRAINT_TYPE);
cookieTokenName = settings.get("auth.token.cookie",
DEFAULT_COOKIE_TOKEN_NAME);
updateToken = settings.getAsBoolean("auth.token.update_by_request",
true);
guestRole = settings.get("auth.role.guest", DEFAULT_GUEST_ROLE);
if (cookieTokenName.trim().length() == 0
|| "false".equalsIgnoreCase(cookieTokenName)) {
cookieToken = false;
}
}
@Override
protected void doStart() throws ElasticsearchException {
logger.info("Starting AuthService.");
final LoginFilter loginFilter = new LoginFilter(this, authenticatorMap);
final String loginPath = settings.get("auth.login.path");
if (loginPath != null) {
loginFilter.setLoginPath(loginPath);
}
final String[] loginMethodValues = settings
.getAsArray("auth.login.methods");
if (loginMethodValues != null && loginMethodValues.length > 0) {
loginFilter.setHttpMethods(createMethods(loginMethodValues));
}
restController.registerFilter(loginFilter);
final LogoutFilter logoutFilter = new LogoutFilter(this);
final String logoutPath = settings.get("auth.logout.path");
if (logoutPath != null) {
logoutFilter.setLogoutPath(logoutPath);
}
final String[] logoutMethodValues = settings
.getAsArray("auth.logout.methods");
if (logoutMethodValues != null && logoutMethodValues.length > 0) {
logoutFilter.setHttpMethods(createMethods(logoutMethodValues));
}
restController.registerFilter(logoutFilter);
contentFilter = new ContentFilter(this);
restController.registerFilter(contentFilter);
}
@Override
protected void doStop() throws ElasticsearchException {
logger.info("Stopping AuthService");
}
@Override
protected void doClose() throws ElasticsearchException {
logger.info("Closing AuthService.");
}
public void registerAuthenticator(final String name,
final Authenticator authenticator) {
authenticatorMap.put(name, authenticator);
}
public void init(final ActionListener listener) {
client.admin().cluster().prepareHealth().setWaitForYellowStatus()
.execute(new ActionListener() {
@Override
public void onResponse(final ClusterHealthResponse response) {
if (response.getStatus() == ClusterHealthStatus.RED) {
listener.onFailure(new AuthException(
RestStatus.SERVICE_UNAVAILABLE,
"This cluster is not ready."));
} else {
createConstraintIndexIfNotExist(listener);
}
}
@Override
public void onFailure(final Throwable e) {
listener.onFailure(e);
}
});
}
protected void createConstraintIndexIfNotExist(
final ActionListener listener) {
client.admin().indices().prepareExists(constraintIndex)
.execute(new ActionListener() {
@Override
public void onResponse(final IndicesExistsResponse response) {
if (response.isExists()) {
reload(listener);
} else {
createConstraintIndex(listener);
}
}
@Override
public void onFailure(final Throwable e) {
listener.onFailure(e);
}
});
}
protected void createConstraintIndex(final ActionListener listener) {
client.admin().indices().prepareCreate(constraintIndex)
.execute(new ActionListener() {
@Override
public void onResponse(final CreateIndexResponse response) {
// TODO health check
reload(listener);
}
@Override
public void onFailure(final Throwable e) {
listener.onFailure(e);
}
});
}
public void reload(final ActionListener listener) {
client.admin().indices().prepareRefresh(constraintIndex).setForce(true)
.execute(new ActionListener() {
@Override
public void onResponse(final RefreshResponse response) {
loadLoginConstraints(new ActionListener() {
@Override
public void onResponse(
final LoginConstraint[] constraints) {
if (logger.isDebugEnabled()) {
logger.debug("Load {} constraint(s).",
constraints.length);
}
contentFilter.setLoginConstraints(constraints);
listener.onResponse(null);
}
@Override
public void onFailure(final Throwable e) {
listener.onFailure(e);
}
});
}
@Override
public void onFailure(final Throwable e) {
listener.onFailure(e);
}
});
}
public void createToken(final Set roleSet,
final ActionListener listener) {
if (roleSet == null || roleSet.isEmpty()) {
listener.onFailure(new AuthException(RestStatus.BAD_REQUEST,
"Role is empty."));
return;
}
final String token = generateToken();
final Map sourceMap = new HashMap();
sourceMap.put("roles", roleSet);
sourceMap.put("lastModified", new Date());
client.prepareIndex(authTokenIndex, tokenType, token)
.setSource(sourceMap).setRefresh(true)
.execute(new ActionListener() {
@Override
public void onResponse(final IndexResponse response) {
listener.onResponse(token);
}
@Override
public void onFailure(final Throwable e) {
listener.onFailure(e);
}
});
}
public void authenticate(final String token, final String[] roles,
final ActionListener listener) {
if (token == null) {
if (roles != null) {
for (final String role : roles) {
if (guestRole.equals(role)) {
listener.onResponse(true);
return;
}
}
}
listener.onResponse(false);
} else {
client.prepareGet(authTokenIndex, tokenType, token).execute(
new ActionListener() {
@Override
public void onResponse(final GetResponse response) {
final Map sourceMap = response
.getSource();
if (sourceMap != null) {
final String[] tokenRoles = MapUtil.getAsArray(
sourceMap, "roles", new String[0]);
for (final String role : roles) {
for (final String tokenRole : tokenRoles) {
if (role.equals(tokenRole)) {
listener.onResponse(true);
if (updateToken) {
updateToken(token, sourceMap);
}
return;
}
}
}
}
listener.onResponse(false);
}
@Override
public void onFailure(final Throwable e) {
listener.onFailure(e);
}
});
}
}
private void updateToken(final String token,
final Map sourceMap) {
sourceMap.put("lastModified", new Date());
client.prepareIndex(authTokenIndex, tokenType, token)
.setSource(sourceMap)
.execute(new ActionListener() {
@Override
public void onResponse(final IndexResponse response) {
// nothing
}
@Override
public void onFailure(final Throwable e) {
logger.warn("Failed to update token: " + token, e);
}
});
}
public void createUser(final String authenticatorName,
final String username, final String password, final String[] roles,
final ActionListener listener) {
if (authenticatorName == null || username == null || password == null
|| roles == null) {
listener.onFailure(new AuthException(RestStatus.BAD_REQUEST,
"authenticator, username, passowrd or roles is null."));
} else {
getAuthenticator(authenticatorName).createUser(username, password,
roles, listener);
}
}
public void updateUser(final String authenticatorName,
final String username, final String password, final String[] roles,
final ActionListener listener) {
if (authenticatorName == null || username == null || password == null
&& roles == null) {
listener.onFailure(new AuthException(RestStatus.BAD_REQUEST,
"authenticator, username, passowrd or roles are null."));
} else {
getAuthenticator(authenticatorName).updateUser(username, password,
roles, listener);
}
}
public void deleteUser(final String authenticatorName,
final String username, final ActionListener listener) {
if (authenticatorName == null || username == null) {
listener.onFailure(new AuthException(RestStatus.BAD_REQUEST,
"authenticator or username are null."));
} else {
getAuthenticator(authenticatorName).deleteUser(username, listener);
}
}
public void deleteToken(final String token,
final ActionListener listener) {
client.prepareDelete(authTokenIndex, tokenType, token).setRefresh(true)
.execute(new ActionListener() {
@Override
public void onResponse(final DeleteResponse response) {
if (response.isFound()) {
listener.onResponse(null);
} else {
listener.onFailure(new AuthException(
RestStatus.BAD_REQUEST,
"The token does not exist."));
}
}
@Override
public void onFailure(final Throwable e) {
listener.onFailure(e);
}
});
}
public String getToken(final RestRequest request) {
String token = request.param(tokenKey);
// cookie
if (token == null && cookieToken) {
final String cookieString = request
.header(HttpHeaders.Names.COOKIE);
if (cookieString != null) {
final CookieDecoder cookieDecoder = new CookieDecoder();
final Set cookies = cookieDecoder.decode(cookieString);
for (final Cookie cookie : cookies) {
if (cookieTokenName.equals(cookie.getName())) {
token = cookie.getValue();
break;
}
}
}
}
return token;
}
private String generateToken() {
return DigestUtils.sha512Hex(UUID.randomUUID().toString());
}
private Authenticator getAuthenticator(final String authenticatorName) {
final Authenticator authenticator = authenticatorMap
.get(authenticatorName);
if (authenticator == null) {
throw new AuthException(RestStatus.BAD_REQUEST,
"Unknown authenticator: " + authenticatorName);
}
return authenticator;
}
protected void loadLoginConstraints(
final ActionListener listener) {
client.prepareSearch(constraintIndex).setTypes(constraintType)
.setQuery(QueryBuilders.queryString("*:*"))
.execute(new ActionListener() {
@Override
public void onResponse(final SearchResponse response) {
final Map constraintMap = new TreeMap(
new Comparator() {
@Override
public int compare(final String path1,
final String path2) {
final int length1 = path1.length();
final int length2 = path2.length();
if (length1 == length2) {
return -1 * path1.compareTo(path2);
}
return length1 < length2 ? -1 : 1;
}
});
final SearchHits hits = response.getHits();
if (hits.totalHits() != 0) {
for (final SearchHit hit : hits) {
final Map sourceMap = hit
.sourceAsMap();
final List methodList = MapUtil
.getAsList(sourceMap, "methods",
Collections
. emptyList());
final List pathList = MapUtil
.getAsList(sourceMap, "paths",
Collections
. emptyList());
final List roleList = MapUtil
.getAsList(sourceMap, "roles",
Collections
. emptyList());
if (!pathList.isEmpty() && !roleList.isEmpty()) {
for (final String path : pathList) {
LoginConstraint constraint = constraintMap
.get(path);
if (constraint == null) {
constraint = new LoginConstraint();
constraint.setPath(path);
constraintMap.put(path, constraint);
}
constraint.addCondition(methodList
.toArray(new String[methodList
.size()]), roleList
.toArray(new String[roleList
.size()]));
}
} else {
logger.warn("Invaid login settings: "
+ sourceMap);
}
}
}
listener.onResponse(constraintMap.values().toArray(
new LoginConstraint[constraintMap.size()]));
}
@Override
public void onFailure(final Throwable e) {
listener.onFailure(new AuthException(
RestStatus.INTERNAL_SERVER_ERROR,
constraintIndex + ":" + constraintType
+ " is not found.", e));
}
});
}
private Method[] createMethods(final String[] methodValues) {
final List methodList = new ArrayList();
for (final String method : methodValues) {
if ("get".equalsIgnoreCase(method)) {
methodList.add(Method.GET);
} else if ("post".equalsIgnoreCase(method)) {
methodList.add(Method.POST);
} else if ("head".equalsIgnoreCase(method)) {
methodList.add(Method.HEAD);
} else if ("options".equalsIgnoreCase(method)) {
methodList.add(Method.OPTIONS);
} else if ("put".equalsIgnoreCase(method)) {
methodList.add(Method.PUT);
} else if ("delete".equalsIgnoreCase(method)) {
methodList.add(Method.DELETE);
}
}
return methodList.toArray(new Method[methodList.size()]);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy