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); } }