SOLR-9200: Add Delegation Token Support to Solr

This commit is contained in:
Gregory Chanan 2016-06-17 16:49:48 -07:00
parent 58f0fbd376
commit 7bf019a9c2
37 changed files with 2257 additions and 220 deletions

View File

@ -106,6 +106,7 @@ io.netty.netty-all.version = 4.0.36.Final
org.apache.curator.version = 2.8.0
/org.apache.curator/curator-client = ${org.apache.curator.version}
/org.apache.curator/curator-framework = ${org.apache.curator.version}
/org.apache.curator/curator-recipes = ${org.apache.curator.version}
/org.apache.derby/derby = 10.9.1.0

View File

@ -100,6 +100,9 @@ New Features
* SOLR-9275: XML QueryParser support (defType=xmlparser) now extensible via configuration.
(Christine Poerschke)
* SOLR-9200: Add Delegation Token Support to Solr.
(Gregory Chanan)
Bug Fixes
----------------------

View File

@ -134,6 +134,10 @@
<dependency org="antlr" name="antlr" rev="${/antlr/antlr}" conf="test.MiniKdc"/>
<dependency org="net.sf.ehcache" name="ehcache-core" rev="${/net.sf.ehcache/ehcache-core}" conf="test.MiniKdc"/>
<dependency org="org.apache.curator" name="curator-framework" rev="${/org.apache.curator/curator-framework}" conf="compile"/>
<dependency org="org.apache.curator" name="curator-client" rev="${/org.apache.curator/curator-client}" conf="compile"/>
<dependency org="org.apache.curator" name="curator-recipes" rev="${/org.apache.curator/curator-recipes}" conf="compile"/>
<!-- StatsComponents percentiles Dependencies-->
<dependency org="com.tdunning" name="t-digest" rev="${/com.tdunning/t-digest}" conf="compile->*"/>
<!-- SQL Parser -->

View File

@ -17,18 +17,11 @@
package org.apache.solr.security;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.Closeable;
import java.io.IOException;
import java.security.Principal;
import java.util.Map;
import org.apache.http.auth.BasicUserPrincipal;
/**
*
* @lucene.experimental
@ -42,32 +35,20 @@ public abstract class AuthenticationPlugin implements Closeable {
* @param pluginConfig Config parameters, possibly from a ZK source
*/
public abstract void init(Map<String, Object> pluginConfig);
protected void forward(String user, ServletRequest req, ServletResponse rsp,
FilterChain chain) throws IOException, ServletException {
if(user != null) {
final Principal p = new BasicUserPrincipal(user);
req = new HttpServletRequestWrapper((HttpServletRequest) req) {
@Override
public Principal getUserPrincipal() {
return p;
}
};
}
chain.doFilter(req,rsp);
}
/**
* This method must authenticate the request. Upon a successful authentication, this
* This method attempts to authenticate the request. Upon a successful authentication, this
* must call the next filter in the filter chain and set the user principal of the request,
* or else, upon an error or an authentication failure, throw an exception.
*
*
* @param request the http request
* @param response the http response
* @param filterChain the servlet filter chain
* @return false if the request not be processed by Solr (not continue), i.e.
* the response and status code have already been sent.
* @throws Exception any exception thrown during the authentication, e.g. PrivilegedActionException
*/
public abstract void doAuthenticate(ServletRequest request, ServletResponse response,
public abstract boolean doAuthenticate(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws Exception;

View File

@ -99,7 +99,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
}
@Override
public void doAuthenticate(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws Exception {
public boolean doAuthenticate(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws Exception {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
@ -127,6 +127,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
}
};
filterChain.doFilter(wrapper, response);
return true;
}
} else {
@ -143,8 +144,10 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
} else {
request.setAttribute(AuthenticationPlugin.class.getName(), zkAuthentication.getPromptHeaders());
filterChain.doFilter(request, response);
return true;
}
}
return false;
}
@Override

View File

@ -0,0 +1,171 @@
/*
* 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.solr.security;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.AuthInfo;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.ACLProvider;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.hadoop.security.authentication.server.AuthenticationHandler;
import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticationFilter;
import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.ZkACLProvider;
import org.apache.solr.common.cloud.ZkCredentialsProvider;
import org.apache.zookeeper.data.ACL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DelegationTokenKerberosFilter extends DelegationTokenAuthenticationFilter {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private CuratorFramework curatorFramework;
@Override
public void init(FilterConfig conf) throws ServletException {
if (conf != null && "zookeeper".equals(conf.getInitParameter("signer.secret.provider"))) {
SolrZkClient zkClient =
(SolrZkClient)conf.getServletContext().getAttribute(KerberosPlugin.DELEGATION_TOKEN_ZK_CLIENT);
conf.getServletContext().setAttribute("signer.secret.provider.zookeeper.curator.client",
getCuratorClient(zkClient));
}
super.init(conf);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
// HttpClient 4.4.x throws NPE if query string is null and parsed through URLEncodedUtils.
// See HTTPCLIENT-1746 and HADOOP-12767
HttpServletRequest httpRequest = (HttpServletRequest)request;
String queryString = httpRequest.getQueryString();
final String nonNullQueryString = queryString == null ? "" : queryString;
HttpServletRequest requestNonNullQueryString = new HttpServletRequestWrapper(httpRequest){
@Override
public String getQueryString() {
return nonNullQueryString;
}
};
super.doFilter(requestNonNullQueryString, response, filterChain);
}
@Override
public void destroy() {
super.destroy();
if (curatorFramework != null) curatorFramework.close();
curatorFramework = null;
}
@Override
protected void initializeAuthHandler(String authHandlerClassName,
FilterConfig filterConfig) throws ServletException {
// set the internal authentication handler in order to record whether the request should continue
super.initializeAuthHandler(authHandlerClassName, filterConfig);
AuthenticationHandler authHandler = getAuthenticationHandler();
super.initializeAuthHandler(KerberosPlugin.RequestContinuesRecorderAuthenticationHandler.class.getName(), filterConfig);
KerberosPlugin.RequestContinuesRecorderAuthenticationHandler newAuthHandler =
(KerberosPlugin.RequestContinuesRecorderAuthenticationHandler)getAuthenticationHandler();
newAuthHandler.setAuthHandler(authHandler);
}
protected CuratorFramework getCuratorClient(SolrZkClient zkClient) {
// should we try to build a RetryPolicy off of the ZkController?
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
if (zkClient == null) {
throw new IllegalArgumentException("zkClient required");
}
String zkHost = zkClient.getZkServerAddress();
String zkChroot = zkHost.substring(zkHost.indexOf("/"));
zkChroot = zkChroot.startsWith("/") ? zkChroot.substring(1) : zkChroot;
String zkNamespace = zkChroot + SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH;
String zkConnectionString = zkHost.substring(0, zkHost.indexOf("/"));
SolrZkToCuratorCredentialsACLs curatorToSolrZk = new SolrZkToCuratorCredentialsACLs(zkClient);
final int connectionTimeoutMs = 30000; // this value is currently hard coded, see SOLR-7561.
curatorFramework = CuratorFrameworkFactory.builder()
.namespace(zkNamespace)
.connectString(zkConnectionString)
.retryPolicy(retryPolicy)
.aclProvider(curatorToSolrZk.getACLProvider())
.authorization(curatorToSolrZk.getAuthInfos())
.sessionTimeoutMs(zkClient.getZkClientTimeout())
.connectionTimeoutMs(connectionTimeoutMs)
.build();
curatorFramework.start();
return curatorFramework;
}
/**
* Convert Solr Zk Credentials/ACLs to Curator versions
*/
protected static class SolrZkToCuratorCredentialsACLs {
private final ACLProvider aclProvider;
private final List<AuthInfo> authInfos;
public SolrZkToCuratorCredentialsACLs(SolrZkClient zkClient) {
this.aclProvider = createACLProvider(zkClient);
this.authInfos = createAuthInfo(zkClient);
}
public ACLProvider getACLProvider() { return aclProvider; }
public List<AuthInfo> getAuthInfos() { return authInfos; }
private ACLProvider createACLProvider(SolrZkClient zkClient) {
final ZkACLProvider zkACLProvider = zkClient.getZkACLProvider();
return new ACLProvider() {
@Override
public List<ACL> getDefaultAcl() {
return zkACLProvider.getACLsToAdd(null);
}
@Override
public List<ACL> getAclForPath(String path) {
List<ACL> acls = zkACLProvider.getACLsToAdd(path);
return acls;
}
};
}
private List<AuthInfo> createAuthInfo(SolrZkClient zkClient) {
List<AuthInfo> ret = new LinkedList<AuthInfo>();
// In theory the credentials to add could change here if zookeeper hasn't been initialized
ZkCredentialsProvider credentialsProvider =
zkClient.getZkClientConnectionStrategy().getZkCredentialsToAddAutomatically();
for (ZkCredentialsProvider.ZkCredentials zkCredentials : credentialsProvider.getCredentials()) {
ret.add(new AuthInfo(zkCredentials.getScheme(), zkCredentials.getAuth()));
}
return ret;
}
}
}

View File

@ -26,6 +26,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
import org.apache.hadoop.security.authentication.server.AuthenticationHandler;
public class KerberosFilter extends AuthenticationFilter {
@ -34,6 +35,19 @@ public class KerberosFilter extends AuthenticationFilter {
super.init(conf);
}
@Override
protected void initializeAuthHandler(String authHandlerClassName,
FilterConfig filterConfig) throws ServletException {
// set the internal authentication handler in order to record whether the request should continue
super.initializeAuthHandler(authHandlerClassName, filterConfig);
AuthenticationHandler authHandler = getAuthenticationHandler();
super.initializeAuthHandler(
KerberosPlugin.RequestContinuesRecorderAuthenticationHandler.class.getName(), filterConfig);
KerberosPlugin.RequestContinuesRecorderAuthenticationHandler newAuthHandler =
(KerberosPlugin.RequestContinuesRecorderAuthenticationHandler)getAuthenticationHandler();
newAuthHandler.setAuthHandler(authHandler);
}
@Override
protected void doFilter(FilterChain filterChain, HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {

View File

@ -16,14 +16,18 @@
*/
package org.apache.solr.security;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.invoke.MethodHandles;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.servlet.Filter;
@ -41,12 +45,22 @@ import javax.servlet.ServletResponse;
import javax.servlet.SessionCookieConfig;
import javax.servlet.SessionTrackingMode;
import javax.servlet.descriptor.JspConfigDescriptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.collections.iterators.IteratorEnumeration;
import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.server.AuthenticationHandler;
import org.apache.hadoop.security.authentication.server.AuthenticationToken;
import org.apache.solr.client.solrj.impl.Krb5HttpClientBuilder;
import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
import org.apache.solr.common.util.SuppressForbidden;
import org.apache.solr.core.CoreContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -55,7 +69,7 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
Krb5HttpClientBuilder kerberosBuilder = new Krb5HttpClientBuilder();
Filter kerberosFilter = new KerberosFilter();
Filter kerberosFilter;
public static final String NAME_RULES_PARAM = "solr.kerberos.name.rules";
public static final String COOKIE_DOMAIN_PARAM = "solr.kerberos.cookie.domain";
@ -64,6 +78,26 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
public static final String KEYTAB_PARAM = "solr.kerberos.keytab";
public static final String TOKEN_VALID_PARAM = "solr.kerberos.token.valid";
public static final String COOKIE_PORT_AWARE_PARAM = "solr.kerberos.cookie.portaware";
public static final String DELEGATION_TOKEN_ENABLED = "solr.kerberos.delegation.token.enabled";
public static final String DELEGATION_TOKEN_KIND = "solr.kerberos.delegation.token.kind";
public static final String DELEGATION_TOKEN_VALIDITY = "solr.kerberos.delegation.token.validity";
public static final String DELEGATION_TOKEN_SECRET_PROVIDER = "solr.kerberos.delegation.token.signer.secret.provider";
public static final String DELEGATION_TOKEN_SECRET_PROVIDER_ZK_PATH =
"solr.kerberos.delegation.token.signer.secret.provider.zookeper.path";
public static final String DELEGATION_TOKEN_SECRET_MANAGER_ZNODE_WORKING_PATH =
"solr.kerberos.delegation.token.secret.manager.znode.working.path";
public static final String DELEGATION_TOKEN_TYPE_DEFAULT = "solr-dt";
// filled in by Plugin/Filter
static final String REQUEST_CONTINUES_ATTR =
"org.apache.solr.security.kerberosplugin.requestcontinues";
static final String DELEGATION_TOKEN_ZK_CLIENT =
"solr.kerberos.delegation.token.zk.client";
// allows test to specify an alternate auth handler
@VisibleForTesting
public static final String AUTH_HANDLER_PARAM = "solr.kerberos.auth.handler";
private final CoreContainer coreContainer;
public KerberosPlugin(CoreContainer coreContainer) {
@ -74,12 +108,47 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
public void init(Map<String, Object> pluginConfig) {
try {
Map<String, String> params = new HashMap();
params.put("type", "kerberos");
putParam(params, "type", AUTH_HANDLER_PARAM, "kerberos");
putParam(params, "kerberos.name.rules", NAME_RULES_PARAM, "DEFAULT");
putParam(params, "token.valid", TOKEN_VALID_PARAM, "30");
putParam(params, "cookie.path", COOKIE_PATH_PARAM, "/");
putParam(params, "kerberos.principal", PRINCIPAL_PARAM, null);
putParam(params, "kerberos.keytab", KEYTAB_PARAM, null);
if ("kerberos".equals(params.get("type"))) {
putParam(params, "kerberos.principal", PRINCIPAL_PARAM, null);
putParam(params, "kerberos.keytab", KEYTAB_PARAM, null);
} else {
// allow tests which specify AUTH_HANDLER_PARAM to avoid specifying kerberos principal/keytab
putParamOptional(params, "kerberos.principal", PRINCIPAL_PARAM);
putParamOptional(params, "kerberos.keytab", KEYTAB_PARAM);
}
String delegationTokenStr = System.getProperty(DELEGATION_TOKEN_ENABLED, null);
boolean delegationTokenEnabled =
(delegationTokenStr == null) ? false : Boolean.parseBoolean(delegationTokenStr);
ZkController controller = coreContainer.getZkController();
if (delegationTokenEnabled) {
putParam(params, "delegation-token.token-kind", DELEGATION_TOKEN_KIND, DELEGATION_TOKEN_TYPE_DEFAULT);
if (coreContainer.isZooKeeperAware()) {
putParam(params, "signer.secret.provider", DELEGATION_TOKEN_SECRET_PROVIDER, "zookeeper");
if ("zookeeper".equals(params.get("signer.secret.provider"))) {
String zkHost = controller.getZkServerAddress();
putParam(params, "token.validity", DELEGATION_TOKEN_VALIDITY, "36000");
params.put("zk-dt-secret-manager.enable", "true");
// Note - Curator complains if the znodeWorkingPath starts with /
String chrootPath = zkHost.substring(zkHost.indexOf("/"));
String relativePath = chrootPath.startsWith("/") ? chrootPath.substring(1) : chrootPath;
putParam(params, "zk-dt-secret-manager.znodeWorkingPath",
DELEGATION_TOKEN_SECRET_MANAGER_ZNODE_WORKING_PATH,
relativePath + SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH + "/zkdtsm");
putParam(params, "signer.secret.provider.zookeeper.path",
DELEGATION_TOKEN_SECRET_PROVIDER_ZK_PATH, "/token");
// ensure krb5 is setup properly before running curator
getHttpClientBuilder(SolrHttpClientBuilder.create());
}
} else {
log.info("CoreContainer is not ZooKeeperAware, not setting ZK-related delegation token properties");
}
}
// Special handling for the "cookie.domain" based on whether port should be
// appended to the domain. Useful for situations where multiple solr nodes are
@ -94,16 +163,27 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
if (host==null) {
throw new SolrException(ErrorCode.SERVER_ERROR, "Missing required parameter '"+COOKIE_DOMAIN_PARAM+"'.");
}
int port = coreContainer.getZkController().getHostPort();
int port = controller.getHostPort();
params.put("cookie.domain", host + ":" + port);
}
final ServletContext servletContext = new AttributeOnlyServletContext();
if (delegationTokenEnabled) {
kerberosFilter = new DelegationTokenKerberosFilter();
// pass an attribute-enabled context in order to pass the zkClient
// and because the filter may pass a curator instance.
if (controller != null) {
servletContext.setAttribute(DELEGATION_TOKEN_ZK_CLIENT, controller.getZkClient());
}
} else {
kerberosFilter = new KerberosFilter();
}
log.info("Params: "+params);
FilterConfig conf = new FilterConfig() {
@Override
public ServletContext getServletContext() {
return noContext;
return servletContext;
}
@Override
@ -136,11 +216,43 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
params.put(internalParamName, value);
}
private void putParamOptional(Map<String, String> params, String internalParamName, String externalParamName) {
String value = System.getProperty(externalParamName);
if (value!=null) {
params.put(internalParamName, value);
}
}
@Override
public void doAuthenticate(ServletRequest req, ServletResponse rsp,
public boolean doAuthenticate(ServletRequest req, ServletResponse rsp,
FilterChain chain) throws Exception {
log.debug("Request to authenticate using kerberos: "+req);
kerberosFilter.doFilter(req, rsp, chain);
final HttpServletResponse frsp = (HttpServletResponse)rsp;
// kerberosFilter may close the stream and write to closed streams,
// see HADOOP-13346. To work around, pass a PrintWriter that ignores
// closes
HttpServletResponse rspCloseShield = new HttpServletResponseWrapper(frsp) {
@SuppressForbidden(reason = "Hadoop DelegationTokenAuthenticationFilter uses response writer, this" +
"is providing a CloseShield on top of that")
@Override
public PrintWriter getWriter() throws IOException {
final PrintWriter pw = new PrintWriterWrapper(frsp.getWriter()) {
@Override
public void close() {};
};
return pw;
}
};
kerberosFilter.doFilter(req, rspCloseShield, chain);
String requestContinuesAttr = (String)req.getAttribute(REQUEST_CONTINUES_ATTR);
if (requestContinuesAttr == null) {
log.warn("Could not find " + REQUEST_CONTINUES_ATTR);
return false;
} else {
return Boolean.parseBoolean(requestContinuesAttr);
}
}
@Override
@ -153,8 +265,9 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
kerberosBuilder.close();
}
protected static ServletContext noContext = new ServletContext() {
protected static class AttributeOnlyServletContext implements ServletContext {
private Map<String, Object> attributes = new HashMap<String, Object>();
@Override
public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes) {}
@ -162,12 +275,16 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
public boolean setInitParameter(String name, String value) {
return false;
}
@Override
public void setAttribute(String name, Object object) {}
public void setAttribute(String name, Object object) {
attributes.put(name, object);
}
@Override
public void removeAttribute(String name) {}
public void removeAttribute(String name) {
attributes.remove(name);
}
@Override
public void log(String message, Throwable throwable) {}
@ -327,15 +444,15 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
public ClassLoader getClassLoader() {
return null;
}
@Override
public Enumeration<String> getAttributeNames() {
return null;
return Collections.enumeration(attributes.keySet());
}
@Override
public Object getAttribute(String name) {
return null;
return attributes.get(name);
}
@Override
@ -395,4 +512,44 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
return null;
}
};
/*
* {@link AuthenticationHandler} that delegates to another {@link AuthenticationHandler}
* and records the response of managementOperation (which indicates whether the request
* should continue or not).
*/
public static class RequestContinuesRecorderAuthenticationHandler implements AuthenticationHandler {
private AuthenticationHandler authHandler;
public void setAuthHandler(AuthenticationHandler authHandler) {
this.authHandler = authHandler;
}
public String getType() {
return authHandler.getType();
}
public void init(Properties config) throws ServletException {
// authHandler has already been init'ed, nothing to do here
}
public void destroy() {
authHandler.destroy();
}
public boolean managementOperation(AuthenticationToken token,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, AuthenticationException {
boolean result = authHandler.managementOperation(token, request, response);
request.setAttribute(KerberosPlugin.REQUEST_CONTINUES_ATTR, new Boolean(result).toString());
return result;
}
public AuthenticationToken authenticate(HttpServletRequest request, HttpServletResponse response)
throws IOException, AuthenticationException {
return authHandler.authenticate(request, response);
}
}
}

View File

@ -89,12 +89,12 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
@SuppressForbidden(reason = "Needs currentTimeMillis to compare against time in header")
@Override
public void doAuthenticate(ServletRequest request, ServletResponse response, FilterChain filterChain) throws Exception {
public boolean doAuthenticate(ServletRequest request, ServletResponse response, FilterChain filterChain) throws Exception {
String requestURI = ((HttpServletRequest) request).getRequestURI();
if (requestURI.endsWith(PATH)) {
filterChain.doFilter(request, response);
return;
return true;
}
long receivedTime = System.currentTimeMillis();
String header = ((HttpServletRequest) request).getHeader(HEADER);
@ -102,14 +102,14 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
//this must not happen
log.error("No SolrAuth header present");
filterChain.doFilter(request, response);
return;
return true;
}
List<String> authInfo = StrUtils.splitWS(header, false);
if (authInfo.size() < 2) {
log.error("Invalid SolrAuth Header {}", header);
filterChain.doFilter(request, response);
return;
return true;
}
String nodeName = authInfo.get(0);
@ -119,12 +119,12 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
if (decipher == null) {
log.error("Could not decipher a header {} . No principal set", header);
filterChain.doFilter(request, response);
return;
return true;
}
if ((receivedTime - decipher.timestamp) > MAX_VALIDITY) {
log.error("Invalid key request timestamp: {} , received timestamp: {} , TTL: {}", decipher.timestamp, receivedTime, MAX_VALIDITY);
filterChain.doFilter(request, response);
return;
return true;
}
final Principal principal = "$".equals(decipher.userName) ?
@ -132,6 +132,7 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
new BasicUserPrincipal(decipher.userName);
filterChain.doFilter(getWrapper((HttpServletRequest) request, principal), response);
return true;
}
private static HttpServletRequestWrapper getWrapper(final HttpServletRequest request, final Principal principal) {

View File

@ -0,0 +1,215 @@
/*
* 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.solr.security;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Locale;
import org.apache.commons.lang.NotImplementedException;
/**
* Wrapper for PrintWriter that delegates to constructor arg
*/
public class PrintWriterWrapper extends PrintWriter {
private PrintWriter printWriter;
public PrintWriterWrapper(PrintWriter printWriter) {
super(new StringWriter());
this.printWriter = printWriter;
}
@Override
public PrintWriter append(char c) {
return printWriter.append(c);
}
@Override
public PrintWriter append(CharSequence csq) {
return printWriter.append(csq);
}
@Override
public PrintWriter append(CharSequence csq, int start, int end) {
return printWriter.append(csq, start, end);
}
@Override
public boolean checkError() {
return printWriter.checkError();
}
@Override
protected void clearError() {
throw new NotImplementedException();
}
@Override
public void close() {
printWriter.close();
}
@Override
public void flush() {
printWriter.flush();
}
@Override
public PrintWriter format(Locale l, String format, Object... args) {
return printWriter.format(l, format, args);
}
@Override
public PrintWriter format(String format, Object... args) {
throw new NotImplementedException("Forbidden API");
}
@Override
public void print(boolean b) {
printWriter.print(b);
}
@Override
public void print(char c) {
printWriter.print(c);
}
@Override
public void print(char[] s) {
printWriter.print(s);
}
@Override
public void print(double d) {
printWriter.print(d);
}
@Override
public void print(float f) {
printWriter.print(f);
}
@Override
public void print(int i) {
printWriter.print(i);
}
@Override
public void print(long l) {
printWriter.print(l);
}
@Override
public void print(Object obj) {
printWriter.print(obj);
}
@Override
public void print(String s) {
printWriter.print(s);
}
@Override
public PrintWriter printf(Locale l, String format, Object... args) {
return printWriter.printf(l, format, args);
}
@Override
public PrintWriter printf(String format, Object... args) {
throw new NotImplementedException("Forbidden API");
}
@Override
public void println() {
printWriter.println();
}
@Override
public void println(boolean x) {
printWriter.println(x);
}
@Override
public void println(char x) {
printWriter.println(x);
}
@Override
public void println(char[] x) {
printWriter.println(x);
}
@Override
public void println(double x) {
printWriter.println(x);
}
@Override
public void println(float x) {
printWriter.println(x);
}
@Override
public void println(int x) {
printWriter.println(x);
}
@Override
public void println(long x) {
printWriter.println(x);
}
@Override
public void println(Object x) {
printWriter.println(x);
}
@Override
public void println(String x) {
printWriter.println(x);
}
@Override
protected void setError() {
throw new NotImplementedException();
}
@Override
public void write(char[] buf) {
printWriter.write(buf);
}
@Override
public void write(char[] buf, int off, int len) {
printWriter.write(buf, off, len);
}
@Override
public void write(int c) {
printWriter.write(c);
}
@Override
public void write(String s) {
printWriter.write(s);
}
@Override
public void write(String s, int off, int len) {
printWriter.write(s, off, len);
}
}

View File

@ -296,6 +296,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
}
private boolean authenticateRequest(ServletRequest request, ServletResponse response, final AtomicReference<ServletRequest> wrappedRequest) throws IOException {
boolean requestContinues = false;
final AtomicBoolean isAuthenticated = new AtomicBoolean(false);
AuthenticationPlugin authenticationPlugin = cores.getAuthenticationPlugin();
if (authenticationPlugin == null) {
@ -308,7 +309,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
try {
log.debug("Request to authenticate: {}, domain: {}, port: {}", request, request.getLocalName(), request.getLocalPort());
// upon successful authentication, this should call the chain's next filter.
authenticationPlugin.doAuthenticate(request, response, new FilterChain() {
requestContinues = authenticationPlugin.doAuthenticate(request, response, new FilterChain() {
public void doFilter(ServletRequest req, ServletResponse rsp) throws IOException, ServletException {
isAuthenticated.set(true);
wrappedRequest.set(req);
@ -319,8 +320,13 @@ public class SolrDispatchFilter extends BaseSolrFilter {
throw new SolrException(ErrorCode.SERVER_ERROR, "Error during request authentication, ", e);
}
}
// failed authentication?
if (!isAuthenticated.get()) {
// requestContinues is an optional short circuit, thus we still need to check isAuthenticated.
// This is because the AuthenticationPlugin doesn't always have enough information to determine if
// it should short circuit, e.g. the Kerberos Authentication Filter will send an error and not
// call later filters in chain, but doesn't throw an exception. We could force each Plugin
// to implement isAuthenticated to simplify the check here, but that just moves the complexity to
// multiple code paths.
if (!requestContinues || !isAuthenticated.get()) {
response.flushBuffer();
return false;
}

View File

@ -16,6 +16,8 @@
*/
package org.apache.solr.cloud;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
@ -24,18 +26,56 @@ import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.base.Preconditions;
import org.apache.hadoop.minikdc.MiniKdc;
import org.apache.solr.client.solrj.impl.Krb5HttpClientBuilder;
public class KerberosTestUtil {
public class KerberosTestServices {
private MiniKdc kdc;
private JaasConfiguration jaasConfiguration;
private Configuration savedConfig;
private Locale savedLocale;
private KerberosTestServices(MiniKdc kdc,
JaasConfiguration jaasConfiguration,
Configuration savedConfig,
Locale savedLocale) {
this.kdc = kdc;
this.jaasConfiguration = jaasConfiguration;
this.savedConfig = savedConfig;
this.savedLocale = savedLocale;
}
public MiniKdc getKdc() {
return kdc;
}
public void start() throws Exception {
if (kdc != null) kdc.start();
Configuration.setConfiguration(jaasConfiguration);
Krb5HttpClientBuilder.regenerateJaasConfiguration();
if (brokenLanguagesWithMiniKdc.contains(Locale.getDefault().getLanguage())) {
Locale.setDefault(Locale.US);
}
}
public void stop() {
if (kdc != null) kdc.stop();
Configuration.setConfiguration(savedConfig);
Krb5HttpClientBuilder.regenerateJaasConfiguration();
Locale.setDefault(savedLocale);
}
public static Builder builder() {
return new Builder();
}
/**
* Returns a MiniKdc that can be used for creating kerberos principals
* and keytabs. Caller is responsible for starting/stopping the kdc.
*/
public static MiniKdc getKdc(File workDir) throws Exception {
private static MiniKdc getKdc(File workDir) throws Exception {
Properties conf = MiniKdc.createConf();
return new MiniKdc(conf, workDir);
}
@ -44,7 +84,7 @@ public class KerberosTestUtil {
* Programmatic version of a jaas.conf file suitable for connecting
* to a SASL-configured zookeeper.
*/
public static class JaasConfiguration extends Configuration {
private static class JaasConfiguration extends Configuration {
private static AppConfigurationEntry[] clientEntry;
private static AppConfigurationEntry[] serverEntry;
@ -60,7 +100,7 @@ public class KerberosTestUtil {
* @param serverKeytab The location of the keytab with the serverPrincipal
*/
public JaasConfiguration(String clientPrincipal, File clientKeytab,
String serverPrincipal, File serverKeytab) {
String serverPrincipal, File serverKeytab) {
Map<String, String> clientOptions = new HashMap();
clientOptions.put("principal", clientPrincipal);
clientOptions.put("keyTab", clientKeytab.getAbsolutePath());
@ -73,9 +113,9 @@ public class KerberosTestUtil {
clientOptions.put("debug", "true");
}
clientEntry = new AppConfigurationEntry[]{
new AppConfigurationEntry(getKrb5LoginModuleName(),
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
clientOptions)};
new AppConfigurationEntry(getKrb5LoginModuleName(),
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
clientOptions)};
if(serverPrincipal!=null && serverKeytab!=null) {
Map<String, String> serverOptions = new HashMap(clientOptions);
serverOptions.put("principal", serverPrincipal);
@ -88,9 +128,9 @@ public class KerberosTestUtil {
}
/**
* Add an entry to the jaas configuration with the passed in principal and keytab,
* Add an entry to the jaas configuration with the passed in principal and keytab,
* along with the app name.
*
*
* @param principal The principal
* @param keytab The keytab containing credentials for the principal
* @param appName The app name of the configuration
@ -127,21 +167,62 @@ public class KerberosTestUtil {
*/
private final static List<String> brokenLanguagesWithMiniKdc =
Arrays.asList(
new Locale("th").getLanguage(),
new Locale("ja").getLanguage(),
new Locale("th").getLanguage(),
new Locale("ja").getLanguage(),
new Locale("hi").getLanguage()
);
/**
*returns the currently set locale, and overrides it with {@link Locale#US} if it's
* currently something MiniKdc can not handle
*
* @see Locale#setDefault
*/
public static final Locale overrideLocaleIfNotSpportedByMiniKdc() {
Locale old = Locale.getDefault();
if (brokenLanguagesWithMiniKdc.contains(Locale.getDefault().getLanguage())) {
Locale.setDefault(Locale.US);
);
public static class Builder {
private File kdcWorkDir;
private String clientPrincipal;
private File clientKeytab;
private String serverPrincipal;
private File serverKeytab;
private String appName;
private Locale savedLocale;
public Builder() {
savedLocale = Locale.getDefault();
}
public Builder withKdc(File kdcWorkDir) {
this.kdcWorkDir = kdcWorkDir;
return this;
}
public Builder withJaasConfiguration(String clientPrincipal, File clientKeytab,
String serverPrincipal, File serverKeytab) {
Preconditions.checkNotNull(clientPrincipal);
Preconditions.checkNotNull(clientKeytab);
this.clientPrincipal = clientPrincipal;
this.clientKeytab = clientKeytab;
this.serverPrincipal = serverPrincipal;
this.serverKeytab = serverKeytab;
this.appName = null;
return this;
}
public Builder withJaasConfiguration(String principal, File keytab, String appName) {
Preconditions.checkNotNull(principal);
Preconditions.checkNotNull(keytab);
this.clientPrincipal = principal;
this.clientKeytab = keytab;
this.serverPrincipal = null;
this.serverKeytab = null;
this.appName = appName;
return this;
}
public KerberosTestServices build() throws Exception {
final MiniKdc kdc = kdcWorkDir != null ? getKdc(kdcWorkDir) : null;
final Configuration oldConfig = clientPrincipal != null ? Configuration.getConfiguration() : null;
JaasConfiguration jaasConfiguration = null;
if (clientPrincipal != null) {
jaasConfiguration = (appName == null) ?
new JaasConfiguration(clientPrincipal, clientKeytab, serverPrincipal, serverKeytab) :
new JaasConfiguration(clientPrincipal, clientKeytab, appName);
}
return new KerberosTestServices(kdc, jaasConfiguration, oldConfig, savedLocale);
}
return old;
}
}

View File

@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.List;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
@ -77,6 +78,7 @@ public class OutOfBoxZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
zkClient.makePath("/protectedMakePathNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
zkClient.create("/unprotectedCreateNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
zkClient.makePath("/unprotectedMakePathNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
zkClient.create(SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH, "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
zkClient.close();
log.info("####SETUP_END " + getTestName());
@ -93,7 +95,9 @@ public class OutOfBoxZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
public void testOutOfBoxSolrZkClient() throws Exception {
SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient, true, true, true, true, true);
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient,
true, true, true, true, true,
true, true, true, true, true);
} finally {
zkClient.close();
}
@ -110,6 +114,7 @@ public class OutOfBoxZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
assertTrue(verifiedList.contains("/solr/unprotectedMakePathNode"));
assertTrue(verifiedList.contains("/solr/protectedMakePathNode"));
assertTrue(verifiedList.contains("/solr/protectedCreateNode"));
assertTrue(verifiedList.contains("/solr" + SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH));
} finally {
zkClient.close();
}

View File

@ -18,18 +18,15 @@ package org.apache.solr.cloud;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.StringUtils;
import org.apache.solr.common.cloud.DefaultZkACLProvider;
import org.apache.solr.common.cloud.DefaultZkCredentialsProvider;
import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider;
import org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider;
import org.apache.solr.common.cloud.ZkACLProvider;
import org.apache.solr.common.cloud.ZkCredentialsProvider;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@ -40,7 +37,6 @@ import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.invoke.MethodHandles;
import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -88,6 +84,7 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
"readonlyACLUsername", "readonlyACLPassword").getSolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
zkClient.create("/protectedCreateNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
zkClient.makePath("/protectedMakePathNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
zkClient.create(SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH, "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
zkClient.close();
zkClient = new SolrZkClientFactoryUsingCompletelyNewProviders(null, null,
@ -114,7 +111,9 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
SolrZkClient zkClient = new SolrZkClientFactoryUsingCompletelyNewProviders(null, null,
null, null).getSolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient, false, false, false, false, false);
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient,
false, false, false, false, false,
false, false, false, false, false);
} finally {
zkClient.close();
}
@ -125,7 +124,9 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
SolrZkClient zkClient = new SolrZkClientFactoryUsingCompletelyNewProviders("connectAndAllACLUsername", "connectAndAllACLPasswordWrong",
null, null).getSolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient, false, false, false, false, false);
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient,
false, false, false, false, false,
false, false, false, false, false);
} finally {
zkClient.close();
}
@ -136,7 +137,9 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
SolrZkClient zkClient = new SolrZkClientFactoryUsingCompletelyNewProviders("connectAndAllACLUsername", "connectAndAllACLPassword",
null, null).getSolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient, true, true, true, true, true);
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient,
true, true, true, true, true,
true, true, true, true, true);
} finally {
zkClient.close();
}
@ -147,7 +150,9 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
SolrZkClient zkClient = new SolrZkClientFactoryUsingCompletelyNewProviders("readonlyACLUsername", "readonlyACLPassword",
null, null).getSolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient, true, true, false, false, false);
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient,
true, true, false, false, false,
false, false, false, false, false);
} finally {
zkClient.close();
}
@ -159,7 +164,9 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
SolrZkClient zkClient = new SolrZkClientUsingVMParamsProvidersButWithDifferentVMParamsNames(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient, false, false, false, false, false);
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient,
false, false, false, false, false,
false, false, false, false, false);
} finally {
zkClient.close();
}
@ -171,7 +178,9 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
SolrZkClient zkClient = new SolrZkClientUsingVMParamsProvidersButWithDifferentVMParamsNames(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient, false, false, false, false, false);
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient,
false, false, false, false, false,
false, false, false, false, false);
} finally {
zkClient.close();
}
@ -183,7 +192,9 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
SolrZkClient zkClient = new SolrZkClientUsingVMParamsProvidersButWithDifferentVMParamsNames(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient, true, true, true, true, true);
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient,
true, true, true, true, true,
true, true, true, true, true);
} finally {
zkClient.close();
}
@ -195,7 +206,9 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
SolrZkClient zkClient = new SolrZkClientUsingVMParamsProvidersButWithDifferentVMParamsNames(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient, true, true, false, false, false);
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient,
true, true, false, false, false,
false, false, false, false, false);
} finally {
zkClient.close();
}
@ -240,28 +253,18 @@ public class OverriddenZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
@Override
public ZkACLProvider createZkACLProvider() {
return new DefaultZkACLProvider() {
return new VMParamsAllAndReadonlyDigestZkACLProvider() {
@Override
protected List<ACL> createGlobalACLsToAdd() {
try {
List<ACL> result = new ArrayList<ACL>();
if (!StringUtils.isEmpty(digestUsername) && !StringUtils.isEmpty(digestPassword)) {
result.add(new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest(digestUsername + ":" + digestPassword))));
}
if (!StringUtils.isEmpty(digestReadonlyUsername) && !StringUtils.isEmpty(digestReadonlyPassword)) {
result.add(new ACL(ZooDefs.Perms.READ, new Id("digest", DigestAuthenticationProvider.generateDigest(digestReadonlyUsername + ":" + digestReadonlyPassword))));
}
if (result.isEmpty()) {
result = ZooDefs.Ids.OPEN_ACL_UNSAFE;
}
return result;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
protected List<ACL> createNonSecurityACLsToAdd() {
return createACLsToAdd(true, digestUsername, digestPassword, digestReadonlyUsername, digestReadonlyPassword);
}
/**
* @return Set of ACLs to return security-related znodes
*/
@Override
protected List<ACL> createSecurityACLsToAdd() {
return createACLsToAdd(false, digestUsername, digestPassword, digestReadonlyUsername, digestReadonlyPassword);
}
};
}

View File

@ -20,15 +20,12 @@ import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.nio.charset.Charset;
import java.util.Locale;
import javax.security.auth.login.Configuration;
import org.apache.hadoop.minikdc.MiniKdc;
import org.apache.lucene.util.Constants;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.cloud.DefaultZkACLProvider;
import org.apache.solr.common.cloud.SaslZkACLProvider;
import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.ZkACLProvider;
import org.apache.solr.util.BadZookeeperThreadsFilter;
@ -50,8 +47,6 @@ public class SaslZkACLProviderTest extends SolrTestCaseJ4 {
private static final Charset DATA_ENCODING = Charset.forName("UTF-8");
protected Locale savedLocale = null;
protected ZkTestServer zkServer;
@BeforeClass
@ -71,7 +66,6 @@ public class SaslZkACLProviderTest extends SolrTestCaseJ4 {
@Override
public void setUp() throws Exception {
super.setUp();
savedLocale = KerberosTestUtil.overrideLocaleIfNotSpportedByMiniKdc();
log.info("####SETUP_START " + getTestName());
createTempDir();
@ -99,6 +93,7 @@ public class SaslZkACLProviderTest extends SolrTestCaseJ4 {
try {
zkClient.create("/protectedCreateNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
zkClient.makePath("/protectedMakePathNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
zkClient.create(SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH, "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
} finally {
zkClient.close();
}
@ -115,7 +110,6 @@ public class SaslZkACLProviderTest extends SolrTestCaseJ4 {
@Override
public void tearDown() throws Exception {
zkServer.shutdown();
Locale.setDefault(savedLocale);
super.tearDown();
}
@ -124,7 +118,9 @@ public class SaslZkACLProviderTest extends SolrTestCaseJ4 {
// Test with Sasl enabled
SolrZkClient zkClient = new SolrZkClientWithACLs(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient, true, true, true, true, true);
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient,
true, true, true, true, true,
true, true, true, true, true);
} finally {
zkClient.close();
}
@ -134,7 +130,9 @@ public class SaslZkACLProviderTest extends SolrTestCaseJ4 {
System.setProperty("zookeeper.sasl.client", "false");
zkClient = new SolrZkClientNoACLs(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient, true, true, false, false, false);
VMParamsZkACLAndCredentialsProvidersTest.doTest(zkClient,
true, true, false, false, false,
false, false, false, false, false);
} finally {
zkClient.close();
System.clearProperty("zookeeper.sasl.client");
@ -176,8 +174,7 @@ public class SaslZkACLProviderTest extends SolrTestCaseJ4 {
*/
public static class SaslZkTestServer extends ZkTestServer {
private String kdcDir;
private MiniKdc kdc;
private Configuration conf;
private KerberosTestServices kerberosTestServices;
public SaslZkTestServer(String zkDir, String kdcDir) {
super(zkDir);
@ -187,13 +184,11 @@ public class SaslZkACLProviderTest extends SolrTestCaseJ4 {
public SaslZkTestServer(String zkDir, int port, String kdcDir) {
super(zkDir, port);
this.kdcDir = kdcDir;
conf = Configuration.getConfiguration();
}
@Override
public void run() throws InterruptedException {
try {
kdc = KerberosTestUtil.getKdc(new File(kdcDir));
// Don't require that credentials match the entire principal string, e.g.
// can match "solr" rather than "solr/host@DOMAIN"
System.setProperty("zookeeper.kerberos.removeRealmFromPrincipal", "true");
@ -202,12 +197,13 @@ public class SaslZkACLProviderTest extends SolrTestCaseJ4 {
String zkClientPrincipal = "solr";
String zkServerPrincipal = "zookeeper/127.0.0.1";
kdc.start();
// Create ZK client and server principals and load them into the Configuration
kdc.createPrincipal(keytabFile, zkClientPrincipal, zkServerPrincipal);
KerberosTestUtil.JaasConfiguration jaas = new KerberosTestUtil.JaasConfiguration(
zkClientPrincipal, keytabFile, zkServerPrincipal, keytabFile);
Configuration.setConfiguration(jaas);
kerberosTestServices = KerberosTestServices.builder()
.withKdc(new File(kdcDir))
.withJaasConfiguration(zkClientPrincipal, keytabFile, zkServerPrincipal, keytabFile)
.build();
kerberosTestServices.start();
kerberosTestServices.getKdc().createPrincipal(keytabFile, zkClientPrincipal, zkServerPrincipal);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
@ -220,8 +216,7 @@ public class SaslZkACLProviderTest extends SolrTestCaseJ4 {
System.clearProperty("zookeeper.authProvider.1");
System.clearProperty("zookeeper.kerberos.removeRealmFromPrincipal");
System.clearProperty("zookeeper.kerberos.removeHostFromPrincipal");
Configuration.setConfiguration(conf);
kdc.stop();
kerberosTestServices.stop();
}
}
}

View File

@ -235,21 +235,23 @@ public class TestAuthenticationFramework extends LuceneTestCase {
public void init(Map<String,Object> pluginConfig) {}
@Override
public void doAuthenticate(ServletRequest request, ServletResponse response, FilterChain filterChain)
public boolean doAuthenticate(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws Exception {
if (expectedUsername == null) {
filterChain.doFilter(request, response);
return;
return true;
}
HttpServletRequest httpRequest = (HttpServletRequest)request;
String username = httpRequest.getHeader("username");
String password = httpRequest.getHeader("password");
log.info("Username: "+username+", password: "+password);
if(MockAuthenticationPlugin.expectedUsername.equals(username) && MockAuthenticationPlugin.expectedPassword.equals(password))
if(MockAuthenticationPlugin.expectedUsername.equals(username) && MockAuthenticationPlugin.expectedPassword.equals(password)) {
filterChain.doFilter(request, response);
else {
return true;
} else {
((HttpServletResponse)response).sendError(401, "Unauthorized request");
return false;
}
}

View File

@ -16,16 +16,13 @@
*/
package org.apache.solr.cloud;
import javax.security.auth.login.Configuration;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.minikdc.MiniKdc;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.LuceneTestCase.SuppressSysoutChecks;
import org.apache.solr.util.BadZookeeperThreadsFilter;
@ -52,17 +49,14 @@ import org.junit.rules.TestRule;
@SuppressSysoutChecks(bugUrl = "Solr logs to JUL")
public class TestMiniSolrCloudClusterKerberos extends TestMiniSolrCloudCluster {
private final Configuration originalConfig = Configuration.getConfiguration();
public TestMiniSolrCloudClusterKerberos () {
NUM_SERVERS = 5;
NUM_SHARDS = 2;
REPLICATION_FACTOR = 2;
}
private MiniKdc kdc;
private KerberosTestServices kerberosTestServices;
private Locale savedLocale; // in case locale is broken and we need to fill in a working locale
@Rule
public TestRule solrTestRules = RuleChain
.outerRule(new SystemPropertiesRestoreRule());
@ -74,20 +68,22 @@ public class TestMiniSolrCloudClusterKerberos extends TestMiniSolrCloudCluster {
@Override
public void setUp() throws Exception {
savedLocale = KerberosTestUtil.overrideLocaleIfNotSpportedByMiniKdc();
super.setUp();
setupMiniKdc();
}
private void setupMiniKdc() throws Exception {
String kdcDir = createTempDir()+File.separator+"minikdc";
kdc = KerberosTestUtil.getKdc(new File(kdcDir));
File keytabFile = new File(kdcDir, "keytabs");
String principal = "HTTP/127.0.0.1";
String zkServerPrincipal = "zookeeper/127.0.0.1";
KerberosTestServices kerberosTestServices = KerberosTestServices.builder()
.withKdc(new File(kdcDir))
.withJaasConfiguration(principal, keytabFile, zkServerPrincipal, keytabFile)
.build();
kdc.start();
kdc.createPrincipal(keytabFile, principal, zkServerPrincipal);
kerberosTestServices.start();
kerberosTestServices.getKdc().createPrincipal(keytabFile, principal, zkServerPrincipal);
String jaas = "Client {\n"
+ " com.sun.security.auth.module.Krb5LoginModule required\n"
@ -109,10 +105,7 @@ public class TestMiniSolrCloudClusterKerberos extends TestMiniSolrCloudCluster {
+ " debug=true\n"
+ " principal=\""+zkServerPrincipal+"\";\n"
+ "};\n";
Configuration conf = new KerberosTestUtil.JaasConfiguration(principal, keytabFile, zkServerPrincipal, keytabFile);
javax.security.auth.login.Configuration.setConfiguration(conf);
String jaasFilePath = kdcDir+File.separator + "jaas-client.conf";
FileUtils.write(new File(jaasFilePath), jaas, StandardCharsets.UTF_8);
System.setProperty("java.security.auth.login.config", jaasFilePath);
@ -156,11 +149,7 @@ public class TestMiniSolrCloudClusterKerberos extends TestMiniSolrCloudCluster {
System.clearProperty("kerberos.principal");
System.clearProperty("kerberos.keytab");
System.clearProperty("authenticationPlugin");
Configuration.setConfiguration(this.originalConfig);
if (kdc != null) {
kdc.stop();
}
Locale.setDefault(savedLocale);
kerberosTestServices.stop();
super.tearDown();
}
}

View File

@ -0,0 +1,405 @@
/*
* 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.solr.cloud;
import junit.framework.Assert;
import org.apache.hadoop.util.Time;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.DelegationTokenRequest;
import org.apache.solr.client.solrj.response.DelegationTokenResponse;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import static org.apache.solr.security.HttpParamDelegationTokenAuthenticationHandler.USER_PARAM;
import org.apache.http.HttpStatus;
import org.apache.solr.security.HttpParamDelegationTokenAuthenticationHandler;
import org.apache.solr.security.KerberosPlugin;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.invoke.MethodHandles;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Test the delegation token support in the {@link org.apache.solr.security.KerberosPlugin}.
*/
@LuceneTestCase.Slow
public class TestSolrCloudWithDelegationTokens extends SolrTestCaseJ4 {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final int NUM_SERVERS = 2;
private static MiniSolrCloudCluster miniCluster;
private static HttpSolrClient solrClientPrimary;
private static HttpSolrClient solrClientSecondary;
@BeforeClass
public static void startup() throws Exception {
System.setProperty("authenticationPlugin", KerberosPlugin.class.getName());
System.setProperty(KerberosPlugin.DELEGATION_TOKEN_ENABLED, "true");
System.setProperty(KerberosPlugin.AUTH_HANDLER_PARAM,
HttpParamDelegationTokenAuthenticationHandler.class.getName());
System.setProperty("solr.kerberos.cookie.domain", "127.0.0.1");
miniCluster = new MiniSolrCloudCluster(NUM_SERVERS, createTempDir(), buildJettyConfig("/solr"));
JettySolrRunner runnerPrimary = miniCluster.getJettySolrRunners().get(0);
solrClientPrimary =
new HttpSolrClient.Builder(runnerPrimary.getBaseUrl().toString())
.build();
JettySolrRunner runnerSecondary = miniCluster.getJettySolrRunners().get(1);
solrClientSecondary =
new HttpSolrClient.Builder(runnerSecondary.getBaseUrl().toString())
.build();
}
@AfterClass
public static void shutdown() throws Exception {
if (miniCluster != null) {
miniCluster.shutdown();
}
miniCluster = null;
solrClientPrimary.close();
solrClientPrimary = null;
solrClientSecondary.close();
solrClientSecondary = null;
System.clearProperty("authenticationPlugin");
System.clearProperty(KerberosPlugin.DELEGATION_TOKEN_ENABLED);
System.clearProperty(KerberosPlugin.AUTH_HANDLER_PARAM);
System.clearProperty("solr.kerberos.cookie.domain");
}
private String getDelegationToken(final String renewer, final String user, HttpSolrClient solrClient) throws Exception {
DelegationTokenRequest.Get get = new DelegationTokenRequest.Get(renewer) {
@Override
public SolrParams getParams() {
ModifiableSolrParams params = new ModifiableSolrParams(super.getParams());
params.set(USER_PARAM, user);
return params;
}
};
DelegationTokenResponse.Get getResponse = get.process(solrClient);
return getResponse.getDelegationToken();
}
private long renewDelegationToken(final String token, final int expectedStatusCode,
final String user, HttpSolrClient client) throws Exception {
DelegationTokenRequest.Renew renew = new DelegationTokenRequest.Renew(token) {
@Override
public SolrParams getParams() {
ModifiableSolrParams params = new ModifiableSolrParams(super.getParams());
params.set(USER_PARAM, user);
return params;
}
@Override
public Set<String> getQueryParams() {
Set<String> queryParams = super.getQueryParams();
queryParams.add(USER_PARAM);
return queryParams;
}
};
try {
DelegationTokenResponse.Renew renewResponse = renew.process(client);
assertEquals(HttpStatus.SC_OK, expectedStatusCode);
return renewResponse.getExpirationTime();
} catch (HttpSolrClient.RemoteSolrException ex) {
assertEquals(expectedStatusCode, ex.code());
return -1;
}
}
private void cancelDelegationToken(String token, int expectedStatusCode, HttpSolrClient client)
throws Exception {
DelegationTokenRequest.Cancel cancel = new DelegationTokenRequest.Cancel(token);
try {
cancel.process(client);
assertEquals(HttpStatus.SC_OK, expectedStatusCode);
} catch (HttpSolrClient.RemoteSolrException ex) {
assertEquals(expectedStatusCode, ex.code());
}
}
private void doSolrRequest(String token, int expectedStatusCode, HttpSolrClient client)
throws Exception {
doSolrRequest(token, expectedStatusCode, client, 1);
}
private void doSolrRequest(String token, int expectedStatusCode, HttpSolrClient client, int trials)
throws Exception {
int lastStatusCode = 0;
for (int i = 0; i < trials; ++i) {
lastStatusCode = getStatusCode(token, null, null, client);
if (lastStatusCode == expectedStatusCode) {
return;
}
Thread.sleep(1000);
}
assertEquals("Did not receieve excepted status code", expectedStatusCode, lastStatusCode);
}
private SolrRequest getAdminRequest(final SolrParams params) {
return new CollectionAdminRequest.List() {
@Override
public SolrParams getParams() {
ModifiableSolrParams p = new ModifiableSolrParams(super.getParams());
p.add(params);
return p;
}
};
}
private int getStatusCode(String token, final String user, final String op, HttpSolrClient client)
throws Exception {
HttpSolrClient delegationTokenServer =
new HttpSolrClient.Builder(client.getBaseURL().toString())
.withDelegationToken(token)
.withResponseParser(client.getParser())
.build();
try {
ModifiableSolrParams p = new ModifiableSolrParams();
if (user != null) p.set(USER_PARAM, user);
if (op != null) p.set("op", op);
SolrRequest req = getAdminRequest(p);
if (user != null || op != null) {
Set<String> queryParams = new HashSet<String>();
if (user != null) queryParams.add(USER_PARAM);
if (op != null) queryParams.add("op");
req.setQueryParams(queryParams);
}
try {
delegationTokenServer.request(req, null, null);
return HttpStatus.SC_OK;
} catch (HttpSolrClient.RemoteSolrException re) {
return re.code();
}
} finally {
delegationTokenServer.close();
}
}
private void doSolrRequest(HttpSolrClient client, SolrRequest request,
int expectedStatusCode) throws Exception {
try {
client.request(request);
assertEquals(HttpStatus.SC_OK, expectedStatusCode);
} catch (HttpSolrClient.RemoteSolrException ex) {
assertEquals(expectedStatusCode, ex.code());
}
}
private void verifyTokenValid(String token) throws Exception {
// pass with token
doSolrRequest(token, HttpStatus.SC_OK, solrClientPrimary);
// fail without token
doSolrRequest(null, ErrorCode.UNAUTHORIZED.code, solrClientPrimary);
// pass with token on other server
doSolrRequest(token, HttpStatus.SC_OK, solrClientSecondary);
// fail without token on other server
doSolrRequest(null, ErrorCode.UNAUTHORIZED.code, solrClientSecondary);
}
/**
* Test basic Delegation Token get/verify
*/
@Test
public void testDelegationTokenVerify() throws Exception {
final String user = "bar";
// Get token
String token = getDelegationToken(null, user, solrClientPrimary);
assertNotNull(token);
verifyTokenValid(token);
}
private void verifyTokenCancelled(String token, HttpSolrClient client) throws Exception {
// fail with token on both servers. If cancelToOtherURL is true,
// the request went to other url, so FORBIDDEN should be returned immediately.
// The cancelled token may take awhile to propogate to the standard url (via ZK).
// This is of course the opposite if cancelToOtherURL is false.
doSolrRequest(token, ErrorCode.FORBIDDEN.code, client, 10);
// fail without token on both servers
doSolrRequest(null, ErrorCode.UNAUTHORIZED.code, solrClientPrimary);
doSolrRequest(null, ErrorCode.UNAUTHORIZED.code, solrClientSecondary);
}
@Test
public void testDelegationTokenCancel() throws Exception {
{
// Get token
String token = getDelegationToken(null, "user", solrClientPrimary);
assertNotNull(token);
// cancel token, note don't need to be authenticated to cancel (no user specified)
cancelDelegationToken(token, HttpStatus.SC_OK, solrClientPrimary);
verifyTokenCancelled(token, solrClientPrimary);
}
{
// cancel token on different server from where we got it
String token = getDelegationToken(null, "user", solrClientPrimary);
assertNotNull(token);
cancelDelegationToken(token, HttpStatus.SC_OK, solrClientSecondary);
verifyTokenCancelled(token, solrClientSecondary);
}
}
@Test
public void testDelegationTokenCancelFail() throws Exception {
// cancel a bogus token
cancelDelegationToken("BOGUS", ErrorCode.NOT_FOUND.code, solrClientPrimary);
{
// cancel twice, first on same server
String token = getDelegationToken(null, "bar", solrClientPrimary);
assertNotNull(token);
cancelDelegationToken(token, HttpStatus.SC_OK, solrClientPrimary);
cancelDelegationToken(token, ErrorCode.NOT_FOUND.code, solrClientSecondary);
cancelDelegationToken(token, ErrorCode.NOT_FOUND.code, solrClientPrimary);
}
{
// cancel twice, first on other server
String token = getDelegationToken(null, "bar", solrClientPrimary);
assertNotNull(token);
cancelDelegationToken(token, HttpStatus.SC_OK, solrClientSecondary);
cancelDelegationToken(token, ErrorCode.NOT_FOUND.code, solrClientSecondary);
cancelDelegationToken(token, ErrorCode.NOT_FOUND.code, solrClientPrimary);
}
}
private void verifyDelegationTokenRenew(String renewer, String user)
throws Exception {
{
// renew on same server
String token = getDelegationToken(renewer, user, solrClientPrimary);
assertNotNull(token);
long now = Time.now();
assertTrue(renewDelegationToken(token, HttpStatus.SC_OK, user, solrClientPrimary) > now);
verifyTokenValid(token);
}
{
// renew on different server
String token = getDelegationToken(renewer, user, solrClientPrimary);
assertNotNull(token);
long now = Time.now();
assertTrue(renewDelegationToken(token, HttpStatus.SC_OK, user, solrClientSecondary) > now);
verifyTokenValid(token);
}
}
@Test
public void testDelegationTokenRenew() throws Exception {
// test with specifying renewer
verifyDelegationTokenRenew("bar", "bar");
// test without specifying renewer
verifyDelegationTokenRenew(null, "bar");
}
@Test
public void testDelegationTokenRenewFail() throws Exception {
// don't set renewer and try to renew as an a different user
String token = getDelegationToken(null, "bar", solrClientPrimary);
assertNotNull(token);
renewDelegationToken(token, ErrorCode.FORBIDDEN.code, "foo", solrClientPrimary);
renewDelegationToken(token, ErrorCode.FORBIDDEN.code, "foo", solrClientSecondary);
// set renewer and try to renew as different user
token = getDelegationToken("renewUser", "bar", solrClientPrimary);
assertNotNull(token);
renewDelegationToken(token, ErrorCode.FORBIDDEN.code, "notRenewUser", solrClientPrimary);
renewDelegationToken(token, ErrorCode.FORBIDDEN.code, "notRenewUser", solrClientSecondary);
}
/**
* Test that a non-delegation-token "op" http param is handled correctly
*/
@Test
public void testDelegationOtherOp() throws Exception {
assertEquals(HttpStatus.SC_OK, getStatusCode(null, "bar", "someSolrOperation", solrClientPrimary));
}
@Test
public void testZNodePaths() throws Exception {
getDelegationToken(null, "bar", solrClientPrimary);
SolrZkClient zkClient = new SolrZkClient(miniCluster.getZkServer().getZkAddress(), 1000);
try {
assertTrue(zkClient.exists("/security/zkdtsm", true));
assertTrue(zkClient.exists("/security/token", true));
} finally {
zkClient.close();
}
}
/**
* Test HttpSolrServer's delegation token support
*/
@Test
public void testDelegationTokenSolrClient() throws Exception {
// Get token
String token = getDelegationToken(null, "bar", solrClientPrimary);
assertNotNull(token);
SolrRequest request = getAdminRequest(new ModifiableSolrParams());
// test without token
HttpSolrClient ss =
new HttpSolrClient.Builder(solrClientPrimary.getBaseURL().toString())
.withResponseParser(solrClientPrimary.getParser())
.build();
try {
doSolrRequest(ss, request, ErrorCode.UNAUTHORIZED.code);
} finally {
ss.close();
}
ss = new HttpSolrClient.Builder(solrClientPrimary.getBaseURL().toString())
.withDelegationToken(token)
.withResponseParser(solrClientPrimary.getParser())
.build();
try {
// test with token via property
doSolrRequest(ss, request, HttpStatus.SC_OK);
// test with param -- should throw an exception
ModifiableSolrParams tokenParam = new ModifiableSolrParams();
tokenParam.set("delegation", "invalidToken");
try {
doSolrRequest(ss, getAdminRequest(tokenParam), ErrorCode.FORBIDDEN.code);
Assert.fail("Expected exception");
} catch (IllegalArgumentException ex) {}
} finally {
ss.close();
}
}
}

View File

@ -17,15 +17,12 @@
package org.apache.solr.cloud;
import java.io.File;
import java.lang.invoke.MethodHandles;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import javax.security.auth.login.Configuration;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.minikdc.MiniKdc;
import org.apache.lucene.index.TieredMergePolicy;
import org.apache.lucene.util.Constants;
import org.apache.lucene.util.LuceneTestCase;
@ -49,6 +46,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule;
@ -67,7 +66,7 @@ import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule;
@LuceneTestCase.SuppressSysoutChecks(bugUrl = "Solr logs to JUL")
public class TestSolrCloudWithKerberosAlt extends LuceneTestCase {
private final Configuration originalConfig = Configuration.getConfiguration();
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected final int NUM_SERVERS;
protected final int NUM_SHARDS;
protected final int REPLICATION_FACTOR;
@ -78,10 +77,8 @@ public class TestSolrCloudWithKerberosAlt extends LuceneTestCase {
REPLICATION_FACTOR = 1;
}
private MiniKdc kdc;
private KerberosTestServices kerberosTestServices;
private Locale savedLocale; // in case locale is broken and we need to fill in a working locale
@Rule
public TestRule solrTestRules = RuleChain
.outerRule(new SystemPropertiesRestoreRule());
@ -98,7 +95,6 @@ public class TestSolrCloudWithKerberosAlt extends LuceneTestCase {
@Override
public void setUp() throws Exception {
savedLocale = KerberosTestUtil.overrideLocaleIfNotSpportedByMiniKdc();
super.setUp();
setupMiniKdc();
}
@ -106,12 +102,15 @@ public class TestSolrCloudWithKerberosAlt extends LuceneTestCase {
private void setupMiniKdc() throws Exception {
System.setProperty("solr.jaas.debug", "true");
String kdcDir = createTempDir()+File.separator+"minikdc";
kdc = KerberosTestUtil.getKdc(new File(kdcDir));
File keytabFile = new File(kdcDir, "keytabs");
String solrServerPrincipal = "HTTP/127.0.0.1";
String solrClientPrincipal = "solr";
kdc.start();
kdc.createPrincipal(keytabFile, solrServerPrincipal, solrClientPrincipal);
File keytabFile = new File(kdcDir, "keytabs");
kerberosTestServices = KerberosTestServices.builder()
.withKdc(new File(kdcDir))
.withJaasConfiguration(solrClientPrincipal, keytabFile, "SolrClient")
.build();
String solrServerPrincipal = "HTTP/127.0.0.1";
kerberosTestServices.start();
kerberosTestServices.getKdc().createPrincipal(keytabFile, solrServerPrincipal, solrClientPrincipal);
String jaas = "SolrClient {\n"
+ " com.sun.security.auth.module.Krb5LoginModule required\n"
@ -124,9 +123,6 @@ public class TestSolrCloudWithKerberosAlt extends LuceneTestCase {
+ " principal=\"" + solrClientPrincipal + "\";\n"
+ "};";
Configuration conf = new KerberosTestUtil.JaasConfiguration(solrClientPrincipal, keytabFile, "SolrClient");
Configuration.setConfiguration(conf);
String jaasFilePath = kdcDir+File.separator+"jaas-client.conf";
FileUtils.write(new File(jaasFilePath), jaas, StandardCharsets.UTF_8);
System.setProperty("java.security.auth.login.config", jaasFilePath);
@ -135,6 +131,9 @@ public class TestSolrCloudWithKerberosAlt extends LuceneTestCase {
System.setProperty("solr.kerberos.principal", solrServerPrincipal);
System.setProperty("solr.kerberos.keytab", keytabFile.getAbsolutePath());
System.setProperty("authenticationPlugin", "org.apache.solr.security.KerberosPlugin");
boolean enableDt = random().nextBoolean();
log.info("Enable delegation token: " + enableDt);
System.setProperty("solr.kerberos.delegation.token.enabled", new Boolean(enableDt).toString());
// Extracts 127.0.0.1 from HTTP/127.0.0.1@EXAMPLE.COM
System.setProperty("solr.kerberos.name.rules", "RULE:[1:$1@$0](.*EXAMPLE.COM)s/@.*//"
+ "\nRULE:[2:$2@$0](.*EXAMPLE.COM)s/@.*//"
@ -240,11 +239,7 @@ public class TestSolrCloudWithKerberosAlt extends LuceneTestCase {
System.clearProperty("authenticationPlugin");
System.clearProperty("solr.kerberos.name.rules");
System.clearProperty("solr.jaas.debug");
Configuration.setConfiguration(this.originalConfig);
if (kdc != null) {
kdc.stop();
}
Locale.setDefault(savedLocale);
kerberosTestServices.stop();
super.tearDown();
}
}

View File

@ -21,6 +21,7 @@ import java.lang.invoke.MethodHandles;
import java.nio.charset.Charset;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider;
import org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider;
@ -76,6 +77,8 @@ public class VMParamsZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
zkClient.create("/protectedCreateNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
zkClient.makePath("/protectedMakePathNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
zkClient.create(SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH, "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false);
zkClient.close();
clearSecuritySystemProperties();
@ -106,7 +109,9 @@ public class VMParamsZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
doTest(zkClient, false, false, false, false, false);
doTest(zkClient,
false, false, false, false, false,
false, false, false, false, false);
} finally {
zkClient.close();
}
@ -118,7 +123,9 @@ public class VMParamsZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
doTest(zkClient, false, false, false, false, false);
doTest(zkClient,
false, false, false, false, false,
false, false, false, false, false);
} finally {
zkClient.close();
}
@ -130,7 +137,9 @@ public class VMParamsZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
doTest(zkClient, true, true, true, true, true);
doTest(zkClient,
true, true, true, true, true,
true, true, true, true, true);
} finally {
zkClient.close();
}
@ -142,17 +151,23 @@ public class VMParamsZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT);
try {
doTest(zkClient, true, true, false, false, false);
doTest(zkClient,
true, true, false, false, false,
false, false, false, false, false);
} finally {
zkClient.close();
}
}
protected static void doTest(SolrZkClient zkClient, boolean getData, boolean list, boolean create, boolean setData, boolean delete) throws Exception {
protected static void doTest(
SolrZkClient zkClient,
boolean getData, boolean list, boolean create, boolean setData, boolean delete,
boolean secureGet, boolean secureList, boolean secureCreate, boolean secureSet, boolean secureDelete) throws Exception {
doTest(zkClient, "/protectedCreateNode", getData, list, create, setData, delete);
doTest(zkClient, "/protectedMakePathNode", getData, list, create, setData, delete);
doTest(zkClient, "/unprotectedCreateNode", true, true, true, true, delete);
doTest(zkClient, "/unprotectedMakePathNode", true, true, true, true, delete);
doTest(zkClient, SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH, secureGet, secureList, secureCreate, secureSet, secureDelete);
}
protected static void doTest(SolrZkClient zkClient, String path, boolean getData, boolean list, boolean create, boolean setData, boolean delete) throws Exception {

View File

@ -0,0 +1,109 @@
/*
* 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.solr.security;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.server.AuthenticationHandler;
import org.apache.hadoop.security.authentication.server.AuthenticationToken;
import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticationHandler;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
/**
* AuthenticationHandler that supports delegation tokens and simple
* authentication via the "user" http parameter
*/
public class HttpParamDelegationTokenAuthenticationHandler extends
DelegationTokenAuthenticationHandler {
public static final String USER_PARAM = "user";
public HttpParamDelegationTokenAuthenticationHandler() {
super(new HttpParamAuthenticationHandler());
}
@Override
public void init(Properties config) throws ServletException {
Properties conf = new Properties();
for (Map.Entry entry : config.entrySet()) {
conf.setProperty((String) entry.getKey(), (String) entry.getValue());
}
conf.setProperty(TOKEN_KIND, KerberosPlugin.DELEGATION_TOKEN_TYPE_DEFAULT);
super.init(conf);
}
private static String getHttpParam(HttpServletRequest request, String param) {
List<NameValuePair> pairs =
URLEncodedUtils.parse(request.getQueryString(), Charset.forName("UTF-8"));
for (NameValuePair nvp : pairs) {
if(param.equals(nvp.getName())) {
return nvp.getValue();
}
}
return null;
}
private static class HttpParamAuthenticationHandler
implements AuthenticationHandler {
@Override
public String getType() {
return "dummy";
}
@Override
public void init(Properties config) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public boolean managementOperation(AuthenticationToken token,
HttpServletRequest request, HttpServletResponse response)
throws IOException, AuthenticationException {
return false;
}
@Override
public AuthenticationToken authenticate(HttpServletRequest request,
HttpServletResponse response)
throws IOException, AuthenticationException {
AuthenticationToken token = null;
String userName = getHttpParam(request, USER_PARAM);
if (userName != null) {
return new AuthenticationToken(userName, userName, "test");
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setHeader("WWW-Authenticate", "dummy");
}
return token;
}
}
}

View File

@ -20,11 +20,16 @@ import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.security.Principal;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import org.apache.http.auth.BasicUserPrincipal;
public class MockAuthenticationPlugin extends AuthenticationPlugin {
static Predicate<ServletRequest> predicate;
@ -33,7 +38,7 @@ public class MockAuthenticationPlugin extends AuthenticationPlugin {
}
@Override
public void doAuthenticate(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
public boolean doAuthenticate(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
String user = null;
if (predicate != null) {
if (predicate.test(request)) {
@ -41,9 +46,32 @@ public class MockAuthenticationPlugin extends AuthenticationPlugin {
request.removeAttribute(Principal.class.getName());
}
}
forward(user, request, response, filterChain);
final FilterChain ffc = filterChain;
final AtomicBoolean requestContinues = new AtomicBoolean(false);
forward(user, request, response, new FilterChain() {
@Override
public void doFilter(ServletRequest req, ServletResponse res) throws IOException, ServletException {
ffc.doFilter(req, res);
requestContinues.set(true);
}
});
return requestContinues.get();
}
protected void forward(String user, ServletRequest req, ServletResponse rsp,
FilterChain chain) throws IOException, ServletException {
if(user != null) {
final Principal p = new BasicUserPrincipal(user);
req = new HttpServletRequestWrapper((HttpServletRequest) req) {
@Override
public Principal getUserPrincipal() {
return p;
}
};
}
chain.doFilter(req,rsp);
}
@Override
public void close() throws IOException {

View File

@ -0,0 +1 @@
c563e25fb37f85a6b029bc9746e75573640474fb

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -0,0 +1,5 @@
Apache Curator
Copyright 2013-2014 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
@ -40,6 +41,10 @@
<dependency org="org.slf4j" name="slf4j-log4j12" rev="${/org.slf4j/slf4j-log4j12}" conf="test"/>
<dependency org="com.fasterxml.jackson.core" name="jackson-annotations" rev="${/com.fasterxml.jackson.core/jackson-annotations}" conf="compile"/>
<dependency org="com.fasterxml.jackson.core" name="jackson-core" rev="${/com.fasterxml.jackson.core/jackson-core}" conf="compile"/>
<dependency org="com.fasterxml.jackson.core" name="jackson-databind" rev="${/com.fasterxml.jackson.core/jackson-databind}" conf="compile"/>
<dependency org="com.google.guava" name="guava" rev="${/com.google.guava/guava}" conf="compile"/>
<exclude org="*" ext="*" matcher="regexp" type="${ivy.exclude.types}"/>
</dependencies>
</ivy-module>

View File

@ -23,6 +23,7 @@ import java.lang.invoke.MethodHandles;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
@ -30,6 +31,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@ -743,7 +745,44 @@ public class HttpSolrClient extends SolrClient {
super(code, "Error from server at " + remoteHost + ": " + msg, th);
}
}
private static class DelegationTokenHttpSolrClient extends HttpSolrClient {
private final String DELEGATION_TOKEN_PARAM = "delegation";
private final String delegationToken;
public DelegationTokenHttpSolrClient(String baseURL,
HttpClient client,
ResponseParser parser,
boolean allowCompression,
String delegationToken) {
super(baseURL, client, parser, allowCompression);
if (delegationToken == null) {
throw new IllegalArgumentException("Delegation token cannot be null");
}
this.delegationToken = delegationToken;
setQueryParams(new TreeSet<String>(Arrays.asList(DELEGATION_TOKEN_PARAM)));
invariantParams = new ModifiableSolrParams();
invariantParams.set(DELEGATION_TOKEN_PARAM, delegationToken);
}
@Override
protected HttpRequestBase createMethod(final SolrRequest request, String collection) throws IOException, SolrServerException {
SolrParams params = request.getParams();
if (params.getParams(DELEGATION_TOKEN_PARAM) != null) {
throw new IllegalArgumentException(DELEGATION_TOKEN_PARAM + " parameter not supported");
}
return super.createMethod(request, collection);
}
@Override
public void setQueryParams(Set<String> queryParams) {
if (queryParams == null || !queryParams.contains(DELEGATION_TOKEN_PARAM)) {
throw new IllegalArgumentException("Query params must contain " + DELEGATION_TOKEN_PARAM);
}
super.setQueryParams(queryParams);
}
}
/**
* Constructs {@link HttpSolrClient} instances from provided configuration.
*/
@ -752,6 +791,7 @@ public class HttpSolrClient extends SolrClient {
private HttpClient httpClient;
private ResponseParser responseParser;
private boolean compression;
private String delegationToken;
/**
* Create a Builder object, based on the provided Solr URL.
@ -788,7 +828,14 @@ public class HttpSolrClient extends SolrClient {
this.compression = compression;
return this;
}
/**
* Use a delegation token for authenticating via the KerberosPlugin
*/
public Builder withDelegationToken(String delegationToken) {
this.delegationToken = delegationToken;
return this;
}
/**
* Create a {@link HttpSolrClient} based on provided configuration.
*/
@ -796,7 +843,11 @@ public class HttpSolrClient extends SolrClient {
if (baseSolrUrl == null) {
throw new IllegalArgumentException("Cannot create HttpSolrClient without a valid baseSolrUrl!");
}
return new HttpSolrClient(baseSolrUrl, httpClient, responseParser, compression);
if (delegationToken == null) {
return new HttpSolrClient(baseSolrUrl, httpClient, responseParser, compression);
} else {
return new DelegationTokenHttpSolrClient(baseSolrUrl, httpClient, responseParser, compression, delegationToken);
}
}
}
}

View File

@ -26,6 +26,7 @@ import java.util.Set;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import com.google.common.annotations.VisibleForTesting;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequestInterceptor;
@ -51,12 +52,21 @@ public class Krb5HttpClientBuilder {
public static final String LOGIN_CONFIG_PROP = "java.security.auth.login.config";
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final Configuration jaasConfig = new SolrJaasConfiguration();
private static Configuration jaasConfig = new SolrJaasConfiguration();
public Krb5HttpClientBuilder() {
}
/**
* The jaasConfig is static, which makes it problematic for testing in the same jvm.
* Call this function to regenerate the static config (this is not thread safe).
*/
@VisibleForTesting
public static void regenerateJaasConfiguration() {
jaasConfig = new SolrJaasConfiguration();
}
public SolrHttpClientBuilder getBuilder() {
return getBuilder(HttpClientUtil.getHttpClientBuilder());
}
@ -104,9 +114,9 @@ public class Krb5HttpClientBuilder {
return null;
}
};
HttpClientUtil.setCookiePolicy(SolrPortAwareCookieSpecFactory.POLICY_NAME);
builder.setCookieSpecRegistryProvider(() -> {
SolrPortAwareCookieSpecFactory cookieFactory = new SolrPortAwareCookieSpecFactory();

View File

@ -0,0 +1,152 @@
/*
* 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.solr.client.solrj.request;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import java.util.TreeSet;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.impl.NoOpResponseParser;
import org.apache.solr.client.solrj.response.DelegationTokenResponse;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ContentStream;
/**
* Class for making Solr delegation token requests.
*
* @since Solr 6.2
*/
public abstract class DelegationTokenRequest
<Q extends DelegationTokenRequest<Q,R>, R extends DelegationTokenResponse>
extends SolrRequest<R> {
protected static final String OP_KEY = "op";
protected static final String TOKEN_KEY = "token";
public DelegationTokenRequest(METHOD m) {
// path doesn't really matter -- the filter will respond to any path.
// setting the path to admin/collections lets us pass through CloudSolrServer
// without having to specify a collection (that may not even exist yet).
super(m, "/admin/collections");
}
protected abstract Q getThis();
/**
* {@inheritDoc}
*/
@Override
public Collection<ContentStream> getContentStreams() throws IOException {
return null;
}
@Override
protected abstract R createResponse(SolrClient client);
public static class Get extends DelegationTokenRequest<Get, DelegationTokenResponse.Get> {
protected String renewer;
public Get() {
this(null);
}
public Get(String renewer) {
super(METHOD.GET);
this.renewer = renewer;
setResponseParser(new DelegationTokenResponse.JsonMapResponseParser());
setQueryParams(new TreeSet<String>(Arrays.asList(OP_KEY)));
}
@Override
protected Get getThis() {
return this;
}
@Override
public SolrParams getParams() {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(OP_KEY, "GETDELEGATIONTOKEN");
if (renewer != null) params.set("renewer", renewer);
return params;
}
@Override
public DelegationTokenResponse.Get createResponse(SolrClient client) { return new DelegationTokenResponse.Get(); }
}
public static class Renew extends DelegationTokenRequest<Renew, DelegationTokenResponse.Renew> {
protected String token;
@Override
protected Renew getThis() {
return this;
}
public Renew(String token) {
super(METHOD.PUT);
this.token = token;
setResponseParser(new DelegationTokenResponse.JsonMapResponseParser());
setQueryParams(new TreeSet<String>(Arrays.asList(OP_KEY, TOKEN_KEY)));
}
@Override
public SolrParams getParams() {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(OP_KEY, "RENEWDELEGATIONTOKEN");
params.set(TOKEN_KEY, token);
return params;
}
@Override
public DelegationTokenResponse.Renew createResponse(SolrClient client) { return new DelegationTokenResponse.Renew(); }
}
public static class Cancel extends DelegationTokenRequest<Cancel, DelegationTokenResponse.Cancel> {
protected String token;
public Cancel(String token) {
super(METHOD.PUT);
this.token = token;
setResponseParser(new NoOpResponseParser());
Set<String> queryParams = new TreeSet<String>();
setQueryParams(new TreeSet<String>(Arrays.asList(OP_KEY, TOKEN_KEY)));
}
@Override
protected Cancel getThis() {
return this;
}
@Override
public SolrParams getParams() {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(OP_KEY, "CANCELDELEGATIONTOKEN");
params.set(TOKEN_KEY, token);
return params;
}
@Override
public DelegationTokenResponse.Cancel createResponse(SolrClient client) { return new DelegationTokenResponse.Cancel(); }
}
}

View File

@ -0,0 +1,108 @@
/*
* 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.solr.client.solrj.response;
import org.apache.solr.client.solrj.ResponseParser;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Map;
/**
* Delegation Token responses
*/
public abstract class DelegationTokenResponse extends SolrResponseBase {
public static class Get extends DelegationTokenResponse {
/**
* Get the urlString to be used as the delegation token
*/
public String getDelegationToken() {
try {
Map map = (Map)getResponse().get("Token");
if (map != null) {
return (String)map.get("urlString");
}
} catch (ClassCastException e) {
throw new SolrException (SolrException.ErrorCode.SERVER_ERROR,
"parsing error", e);
}
return null;
}
}
public static class Renew extends DelegationTokenResponse {
public Long getExpirationTime() {
try {
return (Long)getResponse().get("long");
} catch (ClassCastException e) {
throw new SolrException (SolrException.ErrorCode.SERVER_ERROR,
"parsing error", e);
}
}
}
public static class Cancel extends DelegationTokenResponse {
}
/**
* ResponseParser for JsonMaps. Used for Get and Renew DelegationToken responses.
*/
public static class JsonMapResponseParser extends ResponseParser {
@Override
public String getWriterType() {
return "json";
}
@Override
public NamedList<Object> processResponse(InputStream body, String encoding) {
ObjectMapper mapper = new ObjectMapper();
Map map = null;
try {
map = mapper.readValue(body, Map.class);
} catch (IOException e) {
throw new SolrException (SolrException.ErrorCode.SERVER_ERROR,
"parsing error", e);
}
NamedList<Object> list = new NamedList<Object>();
list.addAll(map);
return list;
}
@Override
public NamedList<Object> processResponse(Reader reader) {
throw new RuntimeException("Cannot handle character stream");
}
@Override
public String getContentType() {
return "application/json";
}
@Override
public String getVersion() {
return "1";
}
}
}

View File

@ -30,19 +30,22 @@ import org.apache.zookeeper.data.Id;
* configurations have already been set up and will not be modified, or
* where configuration changes are controlled via Solr APIs.
*/
public class SaslZkACLProvider extends DefaultZkACLProvider {
public class SaslZkACLProvider extends SecurityAwareZkACLProvider {
private static String superUser = System.getProperty("solr.authorization.superuser", "solr");
@Override
protected List<ACL> createGlobalACLsToAdd() {
List<ACL> result = new ArrayList<ACL>();
result.add(new ACL(ZooDefs.Perms.ALL, new Id("sasl", superUser)));
result.add(new ACL(ZooDefs.Perms.READ, ZooDefs.Ids.ANYONE_ID_UNSAFE));
protected List<ACL> createNonSecurityACLsToAdd() {
List<ACL> ret = new ArrayList<ACL>();
ret.add(new ACL(ZooDefs.Perms.ALL, new Id("sasl", superUser)));
ret.add(new ACL(ZooDefs.Perms.READ, ZooDefs.Ids.ANYONE_ID_UNSAFE));
return ret;
}
if (result.isEmpty()) {
result = super.createGlobalACLsToAdd();
}
return result;
@Override
protected List<ACL> createSecurityACLsToAdd() {
List<ACL> ret = new ArrayList<ACL>();
ret.add(new ACL(ZooDefs.Perms.ALL, new Id("sasl", superUser)));
return ret;
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.solr.common.cloud;
import java.util.List;
import org.apache.zookeeper.data.ACL;
/**
* {@link ZkACLProvider} capable of returning a different set of
* {@link ACL}s for security-related znodes (default: subtree under /security)
* vs non-security-related znodes.
*/
public abstract class SecurityAwareZkACLProvider implements ZkACLProvider {
public static final String SECURITY_ZNODE_PATH = "/security";
private List<ACL> nonSecurityACLsToAdd;
private List<ACL> securityACLsToAdd;
@Override
public List<ACL> getACLsToAdd(String zNodePath) {
if (isSecurityZNodePath(zNodePath)) {
return getSecurityACLsToAdd();
} else {
return getNonSecurityACLsToAdd();
}
}
protected boolean isSecurityZNodePath(String zNodePath) {
if (zNodePath != null
&& (zNodePath.equals(SECURITY_ZNODE_PATH) || zNodePath.startsWith(SECURITY_ZNODE_PATH + "/"))) {
return true;
}
return false;
}
/**
* @return Set of ACLs to return for non-security related znodes
*/
protected abstract List<ACL> createNonSecurityACLsToAdd();
/**
* @return Set of ACLs to return security-related znodes
*/
protected abstract List<ACL> createSecurityACLsToAdd();
private List<ACL> getNonSecurityACLsToAdd() {
if (nonSecurityACLsToAdd == null) {
synchronized (this) {
if (nonSecurityACLsToAdd == null) nonSecurityACLsToAdd = createNonSecurityACLsToAdd();
}
}
return nonSecurityACLsToAdd;
}
private List<ACL> getSecurityACLsToAdd() {
if (securityACLsToAdd == null) {
synchronized (this) {
if (securityACLsToAdd == null) securityACLsToAdd = createSecurityACLsToAdd();
}
}
return securityACLsToAdd;
}
}

View File

@ -122,12 +122,12 @@ public class SolrZkClient implements Closeable {
public SolrZkClient(String zkServerAddress, int zkClientTimeout, int clientConnectTimeout,
ZkClientConnectionStrategy strat, final OnReconnect onReconnect, BeforeReconnect beforeReconnect, ZkACLProvider zkACLProvider) {
this.zkClientConnectionStrategy = strat;
this.zkServerAddress = zkServerAddress;
if (strat == null) {
strat = new DefaultConnectionStrategy();
}
this.zkClientConnectionStrategy = strat;
if (!strat.hasZkCredentialsToAddAutomatically()) {
ZkCredentialsProvider zkCredentialsToAddAutomatically = createZkCredentialsToAddAutomatically();

View File

@ -20,13 +20,14 @@ import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import com.google.common.annotations.VisibleForTesting;
import org.apache.solr.common.StringUtils;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
public class VMParamsAllAndReadonlyDigestZkACLProvider extends DefaultZkACLProvider {
public class VMParamsAllAndReadonlyDigestZkACLProvider extends SecurityAwareZkACLProvider {
public static final String DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME = "zkDigestReadonlyUsername";
public static final String DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME = "zkDigestReadonlyPassword";
@ -53,29 +54,56 @@ public class VMParamsAllAndReadonlyDigestZkACLProvider extends DefaultZkACLProvi
this.zkDigestReadonlyPasswordVMParamName = zkDigestReadonlyPasswordVMParamName;
}
/**
* @return Set of ACLs to return for non-security related znodes
*/
@Override
protected List<ACL> createGlobalACLsToAdd() {
try {
protected List<ACL> createNonSecurityACLsToAdd() {
return createACLsToAdd(true);
}
/**
* @return Set of ACLs to return security-related znodes
*/
@Override
protected List<ACL> createSecurityACLsToAdd() {
return createACLsToAdd(false);
}
protected List<ACL> createACLsToAdd(boolean includeReadOnly) {
String digestAllUsername = System.getProperty(zkDigestAllUsernameVMParamName);
String digestAllPassword = System.getProperty(zkDigestAllPasswordVMParamName);
String digestReadonlyUsername = System.getProperty(zkDigestReadonlyUsernameVMParamName);
String digestReadonlyPassword = System.getProperty(zkDigestReadonlyPasswordVMParamName);
return createACLsToAdd(includeReadOnly,
digestAllUsername, digestAllPassword,
digestReadonlyUsername, digestReadonlyPassword);
}
@VisibleForTesting
protected List<ACL> createACLsToAdd(boolean includeReadOnly,
String digestAllUsername, String digestAllPassword,
String digestReadonlyUsername, String digestReadonlyPassword) {
try {
List<ACL> result = new ArrayList<ACL>();
// Not to have to provide too much credentials and ACL information to the process it is assumed that you want "ALL"-acls
// added to the user you are using to connect to ZK (if you are using VMParamsSingleSetCredentialsDigestZkCredentialsProvider)
String digestAllUsername = System.getProperty(zkDigestAllUsernameVMParamName);
String digestAllPassword = System.getProperty(zkDigestAllPasswordVMParamName);
if (!StringUtils.isEmpty(digestAllUsername) && !StringUtils.isEmpty(digestAllPassword)) {
result.add(new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest(digestAllUsername + ":" + digestAllPassword))));
}
// Besides that support for adding additional "READONLY"-acls for another user
String digestReadonlyUsername = System.getProperty(zkDigestReadonlyUsernameVMParamName);
String digestReadonlyPassword = System.getProperty(zkDigestReadonlyPasswordVMParamName);
if (!StringUtils.isEmpty(digestReadonlyUsername) && !StringUtils.isEmpty(digestReadonlyPassword)) {
result.add(new ACL(ZooDefs.Perms.READ, new Id("digest", DigestAuthenticationProvider.generateDigest(digestReadonlyUsername + ":" + digestReadonlyPassword))));
if (includeReadOnly) {
// Besides that support for adding additional "READONLY"-acls for another user
if (!StringUtils.isEmpty(digestReadonlyUsername) && !StringUtils.isEmpty(digestReadonlyPassword)) {
result.add(new ACL(ZooDefs.Perms.READ, new Id("digest", DigestAuthenticationProvider.generateDigest(digestReadonlyUsername + ":" + digestReadonlyPassword))));
}
}
if (result.isEmpty()) {
result = super.createGlobalACLsToAdd();
result = ZooDefs.Ids.OPEN_ACL_UNSAFE;
}
return result;

View File

@ -97,7 +97,9 @@ public abstract class ZkClientConnectionStrategy {
public boolean hasZkCredentialsToAddAutomatically() {
return zkCredentialsToAddAutomatically != null;
}
public ZkCredentialsProvider getZkCredentialsToAddAutomatically() { return zkCredentialsToAddAutomatically; }
protected SolrZooKeeper createSolrZooKeeper(final String serverAddress, final int zkClientTimeout,
final Watcher watcher) throws IOException {
SolrZooKeeper result = new SolrZooKeeper(serverAddress, zkClientTimeout, watcher);

View File

@ -0,0 +1,70 @@
/*
* 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.solr.client.solrj.request;
import org.apache.lucene.util.LuceneTestCase;
import org.junit.Test;
/**
* Test for DelegationTokenRequests
*/
public class TestDelegationTokenRequest extends LuceneTestCase {
@Test
public void testGetRequest() throws Exception {
// without renewer
DelegationTokenRequest.Get get = new DelegationTokenRequest.Get();
assertEquals("GETDELEGATIONTOKEN", get.getParams().get("op"));
assertNull(get.getParams().get("renewer"));
// with renewer
final String renewer = "test";
get = new DelegationTokenRequest.Get(renewer);
assertEquals("GETDELEGATIONTOKEN", get.getParams().get("op"));
assertEquals(renewer, get.getParams().get("renewer"));
}
@Test
public void testRenewRequest() throws Exception {
final String token = "testToken";
DelegationTokenRequest.Renew renew = new DelegationTokenRequest.Renew(token);
assertEquals("RENEWDELEGATIONTOKEN", renew.getParams().get("op"));
assertEquals(token, renew.getParams().get("token"));
assertTrue(renew.getQueryParams().contains("op"));
assertTrue(renew.getQueryParams().contains("token"));
// can handle null token
renew = new DelegationTokenRequest.Renew(null);
renew.getParams();
}
@Test
public void testCancelRequest() throws Exception {
final String token = "testToken";
DelegationTokenRequest.Cancel cancel = new DelegationTokenRequest.Cancel(token);
assertEquals("CANCELDELEGATIONTOKEN", cancel.getParams().get("op"));
assertEquals(token, cancel.getParams().get("token"));
assertTrue(cancel.getQueryParams().contains("op"));
assertTrue(cancel.getQueryParams().contains("token"));
// can handle null token
cancel = new DelegationTokenRequest.Cancel(null);
cancel.getParams();
}
}

View File

@ -0,0 +1,138 @@
/*
* 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.solr.client.solrj.response;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.client.solrj.ResponseParser;
import org.apache.solr.client.solrj.request.DelegationTokenRequest;
import org.apache.solr.common.SolrException;
import org.junit.Test;
import org.noggit.CharArr;
import org.noggit.JSONWriter;
public class TestDelegationTokenResponse extends LuceneTestCase {
private void delegationTokenResponse(DelegationTokenRequest request,
DelegationTokenResponse response, String responseBody) throws Exception {
ResponseParser parser = request.getResponseParser();
response.setResponse(parser.processResponse(
IOUtils.toInputStream(responseBody, "UTF-8"), "UTF-8"));
}
private String getNestedMapJson(String outerKey, String innerKey, Object innerValue) {
CharArr out = new CharArr();
JSONWriter w = new JSONWriter(out, 2);
Map<String, Object> innerMap = new HashMap<String, Object>();
innerMap.put(innerKey, innerValue);
Map<String, Map<String, Object>> outerMap = new HashMap<String, Map<String, Object>>();
outerMap.put(outerKey, innerMap);
w.write(outerMap);
return out.toString();
}
private String getMapJson(String key, Object value) {
CharArr out = new CharArr();
JSONWriter w = new JSONWriter(out, 2);
Map<String, Object> map = new HashMap<String, Object>();
map.put(key, value);
w.write(map);
return out.toString();
}
@Test
public void testGetResponse() throws Exception {
DelegationTokenRequest.Get getRequest = new DelegationTokenRequest.Get();
DelegationTokenResponse.Get getResponse = new DelegationTokenResponse.Get();
// not a map
try {
delegationTokenResponse(getRequest, getResponse, "");
getResponse.getDelegationToken();
fail("Expected SolrException");
} catch (SolrException se) {
}
// doesn't have Token outerMap
final String someToken = "someToken";
delegationTokenResponse(getRequest, getResponse, getNestedMapJson("NotToken", "urlString", someToken));
assertNull(getResponse.getDelegationToken());
// Token is not a map
try {
delegationTokenResponse(getRequest, getResponse, getMapJson("Token", someToken));
getResponse.getDelegationToken();
fail("Expected SolrException");
} catch (SolrException se) {
}
// doesn't have urlString
delegationTokenResponse(getRequest, getResponse, getNestedMapJson("Token", "notUrlString", someToken));
assertNull(getResponse.getDelegationToken());
// has Token + urlString
delegationTokenResponse(getRequest, getResponse, getNestedMapJson("Token", "urlString", someToken));
assertEquals(someToken, getResponse.getDelegationToken());
}
@Test
public void testRenewResponse() throws Exception {
DelegationTokenRequest.Renew renewRequest = new DelegationTokenRequest.Renew("token");
DelegationTokenResponse.Renew renewResponse = new DelegationTokenResponse.Renew();
// not a map
try {
delegationTokenResponse(renewRequest, renewResponse, "");
renewResponse.getExpirationTime();
fail("Expected SolrException");
} catch (SolrException se) {
}
// doesn't have long
delegationTokenResponse(renewRequest, renewResponse, getMapJson("notLong", "123"));
assertNull(renewResponse.getExpirationTime());
// long isn't valid
try {
delegationTokenResponse(renewRequest, renewResponse, getMapJson("long", "aaa"));
renewResponse.getExpirationTime();
fail("Expected SolrException");
} catch (SolrException se) {
}
// valid
Long expirationTime = new Long(Long.MAX_VALUE);
delegationTokenResponse(renewRequest, renewResponse,
getMapJson("long", expirationTime));
assertEquals(expirationTime, renewResponse.getExpirationTime());
}
@Test
public void testCancelResponse() throws Exception {
// expect empty response
DelegationTokenRequest.Cancel cancelRequest = new DelegationTokenRequest.Cancel("token");
DelegationTokenResponse.Cancel cancelResponse = new DelegationTokenResponse.Cancel();
delegationTokenResponse(cancelRequest, cancelResponse, "");
}
}