package org.ovirt.engine.core.sso.utils;
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.HashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.api.extensions.aaa.Authz;
import org.ovirt.engine.api.extensions.aaa.Mapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NegotiateAuthUtils {
public static final String STACK_ATTR = NegotiateAuthUtils.class.getName() + ".stack";
public static final String REQUEST_SCHEMES_KEY = "request_schemes";
private static Logger log = LoggerFactory.getLogger(NegotiateAuthUtils.class);
private Set<String> schemes;
private List<AuthenticationProfile> profiles;
private long caps = 0;
public NegotiateAuthUtils(final Collection<AuthenticationProfile> availableProfiles) {
caps |= Authn.Capabilities.AUTHENTICATE_NEGOTIATE_INTERACTIVE |
Authn.Capabilities.AUTHENTICATE_NEGOTIATE_NON_INTERACTIVE;
cacheNegotiatingProfiles(availableProfiles);
}
private void cacheNegotiatingProfiles(final Collection<AuthenticationProfile> availableProfiles) {
schemes = new HashSet<>();
profiles = new ArrayList<>();
for (AuthenticationProfile profile : availableProfiles) {
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));
}
public AuthResult doAuth(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
Deque<AuthenticationProfile> stack = (Deque<AuthenticationProfile>)
request.getAttribute(NegotiateAuthUtils.STACK_ATTR);
request.getSession(true).setAttribute(REQUEST_SCHEMES_KEY, schemes);
if (stack == null) {
stack = new ArrayDeque<>();
stack.addAll(getProfiles());
}
AuthResult retVal = doAuth(request, response, stack);
if (!stack.isEmpty()) {
request.setAttribute(NegotiateAuthUtils.STACK_ATTR, stack);
} else if (retVal.getToken() != null) {
request.removeAttribute(NegotiateAuthUtils.STACK_ATTR);
}
return retVal;
}
private AuthResult doAuth(HttpServletRequest req, HttpServletResponse rsp, Deque<AuthenticationProfile> stack)
throws IOException, ServletException {
log.debug("Performing external authentication");
AuthResult retVal = new AuthResult(Authn.AuthResult.NEGOTIATION_UNAUTHORIZED);
String token = null;
boolean stop = false;
try {
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
)
);
retVal.setStatus(output.<Integer>get(Authn.InvokeKeys.RESULT));
switch (output.<Integer>get(Authn.InvokeKeys.RESULT)) {
case Authn.AuthResult.SUCCESS:
ExtMap authRecord = output.get(Authn.InvokeKeys.AUTH_RECORD);
if (profile.getMapper() != null) {
authRecord = profile.getMapper().invoke(
new ExtMap().mput(
Base.InvokeKeys.COMMAND,
Mapping.InvokeCommands.MAP_AUTH_RECORD
).mput(
Authn.InvokeKeys.AUTH_RECORD,
authRecord
),
true
).get(
Authn.InvokeKeys.AUTH_RECORD,
authRecord
);
}
ExtMap outputMap = profile.getAuthz().invoke(new ExtMap().mput(
Base.InvokeKeys.COMMAND,
Authz.InvokeCommands.FETCH_PRINCIPAL_RECORD
).mput(
Authn.InvokeKeys.AUTH_RECORD,
authRecord
).mput(
Authz.InvokeKeys.QUERY_FLAGS,
Authz.QueryFlags.RESOLVE_GROUPS | Authz.QueryFlags.RESOLVE_GROUPS_RECURSIVE
));
SsoSession ssoSession = SsoUtils.persistAuthInfoInContextWithToken(req,
null,
profile.getName(),
authRecord,
outputMap.get(Authz.InvokeKeys.PRINCIPAL_RECORD));
log.info("User {}@{} successfully logged in with scopes : {} ",
SsoUtils.getUserId(outputMap.get(Authz.InvokeKeys.PRINCIPAL_RECORD)),
profile.getName(),
ssoSession.getScope());
token = (String) req.getAttribute(SsoConstants.HTTP_REQ_ATTR_ACCESS_TOKEN);
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: {}",
output.<Integer>get(Authn.InvokeKeys.RESULT));
stack.pop();
break;
}
}
if (!stack.isEmpty()) {
req.getSession(true).setAttribute(STACK_ATTR, stack);
} else {
req.getSession(true).removeAttribute(STACK_ATTR);
}
} catch (Exception ex) {
log.error("External Authentication Failed: {}", ex.getMessage());
log.debug("External Authentication Failed", ex);
token = null;
}
log.debug("External Authentication result: {}", StringUtils.isNotEmpty(token));
retVal.setToken(token);
return retVal;
}
public List<AuthenticationProfile> getProfiles() {
return new ArrayList<>(profiles);
}
}