package org.ovirt.engine.core.aaa.filters;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.List;
import javax.servlet.Filter;
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.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.ovirt.engine.api.extensions.Base;
import org.ovirt.engine.api.extensions.ExtMap;
import org.ovirt.engine.api.extensions.aaa.Authn;
import org.ovirt.engine.core.aaa.AuthType;
import org.ovirt.engine.core.aaa.AuthenticationProfile;
import org.ovirt.engine.core.aaa.AuthenticationProfileRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This filter should be added to the {@code web.xml} file to the applications that want to use the authentication
* mechanism implemented in this package.
*/
public class NegotiationFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(NegotiationFilter.class);
private static final String CAPABILITIES_PARAMETER = "capabilities";
/**
* In order to support several alternative authentication extension we
* store their associated profiles in a stack inside the HTTP session,
* this is the key for that stack.
*/
private static final String STACK_ATTR = NegotiationFilter.class.getName() + ".stack";
private volatile Collection<String> schemes;
private volatile List<AuthenticationProfile> profiles;
private long caps = 0;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String capsParam = filterConfig.getInitParameter(CAPABILITIES_PARAMETER);
if (capsParam != null) {
for (String nego : capsParam.trim().split(" *\\| *")) {
try {
caps |= Authn.Capabilities.class.getField(nego).getLong(null);
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException ex) {
log.error("Error calculating authn capabilities while accessing constant {}", nego);
}
}
}
AuthenticationProfileRepository.getInstance().addObserver((o, arg) -> cacheNegotiatingProfiles()
);
cacheNegotiatingProfiles();
}
@Override
public void destroy() {
}
private synchronized void cacheNegotiatingProfiles() {
schemes = new ArrayList<>();
profiles = new ArrayList<>();
for (AuthenticationProfile profile : AuthenticationProfileRepository.getInstance().getProfiles()) {
ExtMap authnContext = profile.getAuthn().getContext();
if ((authnContext.<Long> get(Authn.ContextKeys.CAPABILITIES).longValue() & caps) != 0) {
profiles.add(profile);
schemes.addAll(authnContext.<Collection<String>>get(Authn.ContextKeys.HTTP_AUTHENTICATION_SCHEME, Collections.<String>emptyList()));
}
}
Collections.sort(profiles, Comparator.comparing(AuthenticationProfile::getNegotiationPriority));
}
@SuppressWarnings("unchecked")
@Override
public void doFilter(ServletRequest req, ServletResponse rsp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpreq = (HttpServletRequest)req;
if (FiltersHelper.isAuthenticated(httpreq) || httpreq.getAttribute(FiltersHelper.Constants.REQUEST_AUTH_RECORD_KEY) != null) {
chain.doFilter(req, rsp);
} else {
req.setAttribute(FiltersHelper.Constants.REQUEST_SCHEMES_KEY, schemes);
HttpSession session = httpreq.getSession(false);
Deque<AuthenticationProfile> stack = null;
if (session != null) {
stack = (Deque<AuthenticationProfile>)session.getAttribute(STACK_ATTR);
}
if (stack == null) {
stack = new ArrayDeque<>();
stack.addAll(profiles);
}
doAuth(httpreq, (HttpServletResponse) rsp, stack);
if (!stack.isEmpty()) {
httpreq.getSession(true).setAttribute(STACK_ATTR, stack);
} else {
if (session != null) {
session.removeAttribute(STACK_ATTR);
}
chain.doFilter(req, rsp);
}
}
}
private void doAuth(HttpServletRequest req, HttpServletResponse rsp, Deque<AuthenticationProfile> stack)
throws IOException, ServletException {
boolean stop = false;
while (!stop && !stack.isEmpty()) {
AuthenticationProfile profile = stack.peek();
ExtMap output = profile.getAuthn().invoke(
new ExtMap().mput(
Base.InvokeKeys.COMMAND,
Authn.InvokeCommands.AUTHENTICATE_NEGOTIATE
).mput(
Authn.InvokeKeys.HTTP_SERVLET_REQUEST,
req
).mput(
Authn.InvokeKeys.HTTP_SERVLET_RESPONSE,
rsp
)
);
switch (output.<Integer> get(Authn.InvokeKeys.RESULT)) {
case Authn.AuthResult.SUCCESS:
req.setAttribute(FiltersHelper.Constants.REQUEST_AUTH_RECORD_KEY,
output.<ExtMap> get(Authn.InvokeKeys.AUTH_RECORD));
req.setAttribute(FiltersHelper.Constants.REQUEST_AUTH_TYPE_KEY,
AuthType.NEGOTIATION);
req.setAttribute(FiltersHelper.Constants.REQUEST_PROFILE_KEY, profile.getName());
stack.clear();
break;
case Authn.AuthResult.NEGOTIATION_UNAUTHORIZED:
stack.pop();
break;
case Authn.AuthResult.NEGOTIATION_INCOMPLETE:
stop = true;
break;
default:
log.error("Unexpected authentication result. AuthResult code is {}",
output.<Integer> get(Authn.InvokeKeys.RESULT));
stack.pop();
break;
}
}
}
}