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 java.util.Map;
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.apache.commons.lang.StringUtils;
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.AuthenticationProfile;
import org.ovirt.engine.core.aaa.AuthenticationProfileRepository;
import org.ovirt.engine.core.aaa.SsoOAuthServiceUtils;
import org.ovirt.engine.core.aaa.SsoUtils;
import org.ovirt.engine.core.utils.EngineLocalConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SsoRestApiNegotiationFilter implements Filter {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String scope = "ovirt-app-api ovirt-ext=token:login-on-behalf";
/**
* 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 = SsoRestApiNegotiationFilter.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 {
caps |= Authn.Capabilities.AUTHENTICATE_NEGOTIATE_INTERACTIVE | Authn.Capabilities.AUTHENTICATE_NEGOTIATE_NON_INTERACTIVE;
AuthenticationProfileRepository.getInstance().addObserver((o, arg) -> cacheNegotiatingProfiles()
);
cacheNegotiatingProfiles();
}
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));
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.debug("Entered SsoRestApiNegotiationFilter");
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if ((FiltersHelper.isAuthenticated(req) && FiltersHelper.isSessionValid((HttpServletRequest) request)) ||
!EngineLocalConfig.getInstance().getBoolean("ENGINE_RESTAPI_NEGO")) {
log.debug("SsoRestApiNegotiationFilter Not performing Negotiate Auth");
chain.doFilter(request, response);
} else {
log.debug("SsoRestApiNegotiationFilter performing Negotiate Auth");
try {
req.setAttribute(FiltersHelper.Constants.REQUEST_SCHEMES_KEY, schemes);
HttpSession session = req.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);
}
AuthResult authResult = doAuth(req, resp, stack);
if (!stack.isEmpty()) {
req.getSession(true).setAttribute(STACK_ATTR, stack);
} else {
if (session != null) {
session.removeAttribute(STACK_ATTR);
}
if (authResult.username != null) {
log.debug("SsoRestApiNegotiationFilter invoking SsoAuthServiceUtils.loginOnBehalf for : {}", authResult.username);
Map<String, Object> jsonResponse = SsoOAuthServiceUtils.loginOnBehalf(
authResult.username, scope, authResult.authRecord);
FiltersHelper.isStatusOk(jsonResponse);
log.debug("SsoRestApiNegotiationFilter creating user session");
SsoUtils.createUserSession(req, FiltersHelper.getPayloadForToken(
(String) jsonResponse.get("access_token")), false);
}
chain.doFilter(req, resp);
}
} catch (Exception e) {
log.error("Cannot authenticate using External Authentication: {}", e.getMessage());
log.debug("Cannot authenticate using External Authentication", e);
chain.doFilter(req, resp);
}
}
}
private AuthResult doAuth(HttpServletRequest req, HttpServletResponse rsp, Deque<AuthenticationProfile> stack)
throws IOException, ServletException {
AuthResult authResult = new AuthResult();
log.debug("Performing external authentication");
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:
ExtMap authRecord = output.get(Authn.InvokeKeys.AUTH_RECORD);
authResult.authRecord = authRecord;
authResult.username = String.format("%s@%s", authRecord.get(Authn.AuthRecord.PRINCIPAL), 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;
}
}
log.debug("External Authentication result: {}", StringUtils.isNotEmpty(authResult.username));
return authResult;
}
@Override
public void destroy() {
// empty
}
static class AuthResult {
String username;
ExtMap authRecord;
}
}