Ravi Nori has uploaded a new change for review. Change subject: aaa: Add Bearer and Negotiate auth filters ......................................................................
aaa: Add Bearer and Negotiate auth filters This will be merged to patch #2 Change-Id: Idee5137430cefa7ca99c047cfd2d550222e5809a Bug-Url: https://bugzilla.redhat.com/1092744 Signed-off-by: Ravi Nori <rn...@redhat.com> --- M backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/SSOOAuthServiceUtils.java R backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiBaseFilter.java A backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiBasicAuthFilter.java A backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiBearerAuthFilter.java A backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiNegotiationFilter.java M backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml 6 files changed, 326 insertions(+), 22 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/92/42292/1 diff --git a/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/SSOOAuthServiceUtils.java b/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/SSOOAuthServiceUtils.java index 378cc66..0095acc 100644 --- a/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/SSOOAuthServiceUtils.java +++ b/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/SSOOAuthServiceUtils.java @@ -2,8 +2,12 @@ import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; +import org.codehaus.jackson.map.DeserializationConfig; +import org.codehaus.jackson.map.ObjectMapper; +import org.ovirt.engine.api.extensions.ExtMap; import org.ovirt.engine.core.aaa.filters.FiltersHelper; import org.ovirt.engine.core.utils.EngineLocalConfig; +import org.ovirt.engine.core.utils.serialization.json.JsonExtMapMixIn; import org.ovirt.engine.core.utils.serialization.json.JsonObjectDeserializer; import org.ovirt.engine.core.uutils.net.HttpURLConnectionBuilder; @@ -42,6 +46,36 @@ } + public static Map<String, Object> loginOnBehalf(HttpServletRequest req, HttpServletResponse resp, Map<String, Object> payload, String scope) throws Exception { + HttpURLConnection connection = null; + try { + connection = createConnection(req, "/oauth/token"); + setClientIdSecretBasicAuthHeader(connection); + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(SSOOAuthServiceUtils.class.getClassLoader()); + String jsonPayload = ""; + try { + ObjectMapper mapper = new ObjectMapper().configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE); + mapper.getSerializationConfig().addMixInAnnotations(ExtMap.class, JsonExtMapMixIn.class); + jsonPayload = mapper.writeValueAsString(payload); + } finally { + Thread.currentThread().setContextClassLoader(loader); + } + postData(connection, String.format("grant_type=%s&username=%s&password=%s&scope=%s&payload=%s", + resp.encodeURL("password"), + resp.encodeURL((String) payload.get("username")), + resp.encodeURL(""), + resp.encodeURL(scope), + resp.encodeURL(jsonPayload))); + return getData(connection); + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + public static String[] getUserCredentialsFromHeader(HttpServletRequest request) { String header = request.getHeader("Authorization"); String userName = ""; diff --git a/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiLoginFilter.java b/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiBaseFilter.java similarity index 79% rename from backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiLoginFilter.java rename to backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiBaseFilter.java index c10d269..9a52330 100644 --- a/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiLoginFilter.java +++ b/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiBaseFilter.java @@ -1,6 +1,5 @@ package org.ovirt.engine.core.aaa.filters; -import org.ovirt.engine.core.aaa.SSOOAuthServiceUtils; import org.ovirt.engine.core.common.action.CreateUserSessionParameters; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; @@ -23,12 +22,9 @@ import java.util.List; import java.util.Map; -public class SSORestApiLoginFilter implements Filter { - +public abstract class SSORestApiBaseFilter implements Filter { private final Logger log = LoggerFactory.getLogger(getClass()); private boolean loginAsAdmin = false; - private static final String redirectUri = "/ovirt-engine/api?"; - private static final String scope = "ovirt-app-api"; @Override public void init(FilterConfig filterConfig) throws ServletException { @@ -36,8 +32,7 @@ } @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, - ServletException { + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; if (!FiltersHelper.isAuthenticated(req) || !FiltersHelper.isSessionValid((HttpServletRequest) request, (HttpServletResponse) response)) { authenticateWithSSO(req, (HttpServletResponse) response); @@ -45,18 +40,9 @@ chain.doFilter(request, response); } - private void authenticateWithSSO(HttpServletRequest req, HttpServletResponse res) throws ServletException { - try { - Map<String, Object> response = SSOOAuthServiceUtils.authenticate(req, res, scope); - FiltersHelper.isStatusOk(response); - createUserSession(req, FiltersHelper.getPayloadForToken(req, res, (String) response.get("access_token"))); - } catch (Exception e) { - log.error(e.getMessage()); - log.debug("Authentication with SSO failed", e); - } - } + protected abstract void authenticateWithSSO(HttpServletRequest req, HttpServletResponse res) throws ServletException; - private void createUserSession(HttpServletRequest req, Map<String, Object> jsonResponse) { + protected void createUserSession(HttpServletRequest req, Map<String, Object> jsonResponse) { if (!FiltersHelper.isStatusOk(jsonResponse)) { throw new RuntimeException((String) jsonResponse.get("MESSAGE")); } @@ -105,6 +91,6 @@ @Override public void destroy() { + // empty } - } diff --git a/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiBasicAuthFilter.java b/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiBasicAuthFilter.java new file mode 100644 index 0000000..55e8f14 --- /dev/null +++ b/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiBasicAuthFilter.java @@ -0,0 +1,28 @@ +package org.ovirt.engine.core.aaa.filters; + +import org.ovirt.engine.core.aaa.SSOOAuthServiceUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +public class SSORestApiBasicAuthFilter extends SSORestApiBaseFilter { + + private final Logger log = LoggerFactory.getLogger(getClass()); + private static final String scope = "ovirt-app-api"; + + protected void authenticateWithSSO(HttpServletRequest req, HttpServletResponse res) throws ServletException { + try { + Map<String, Object> response = SSOOAuthServiceUtils.authenticate(req, res, scope); + FiltersHelper.isStatusOk(response); + createUserSession(req, FiltersHelper.getPayloadForToken(req, res, (String) response.get("access_token"))); + } catch (Exception e) { + log.error(e.getMessage()); + log.debug("Authentication with SSO failed", e); + } + } + +} diff --git a/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiBearerAuthFilter.java b/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiBearerAuthFilter.java new file mode 100644 index 0000000..2d116dc --- /dev/null +++ b/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiBearerAuthFilter.java @@ -0,0 +1,30 @@ +package org.ovirt.engine.core.aaa.filters; + +import org.apache.commons.codec.binary.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.nio.charset.Charset; + +public class SSORestApiBearerAuthFilter extends SSORestApiBaseFilter { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + protected void authenticateWithSSO(HttpServletRequest req, HttpServletResponse res) throws ServletException { + String headerValue = req.getHeader(FiltersHelper.Constants.HEADER_AUTHORIZATION); + if (headerValue != null && headerValue.startsWith("Bearer ")) { + try { + createUserSession(req, FiltersHelper.getPayloadForToken(req, res, + new String(Base64.decodeBase64(headerValue.substring("Bearer".length())), Charset.forName("UTF-8")) + )); + } catch (Exception e) { + log.error(e.getMessage()); + log.debug("Bearer Authentication with SSO failed", e); + } + } + } + +} diff --git a/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiNegotiationFilter.java b/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiNegotiationFilter.java new file mode 100644 index 0000000..49c24e0 --- /dev/null +++ b/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiNegotiationFilter.java @@ -0,0 +1,208 @@ +package org.ovirt.engine.core.aaa.filters; + +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.ovirt.engine.core.aaa.AuthenticationProfile; +import org.ovirt.engine.core.aaa.AuthenticationProfileRepository; +import org.ovirt.engine.core.aaa.SSOOAuthServiceUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; + +public class SSORestApiNegotiationFilter extends SSORestApiBaseFilter { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private static final String scope = "ovirt-auth-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 = 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 { + caps |= Authn.Capabilities.AUTHENTICATE_NEGOTIATE_INTERACTIVE | Authn.Capabilities.AUTHENTICATE_NEGOTIATE_NON_INTERACTIVE; + + AuthenticationProfileRepository.getInstance().addObserver( + new Observer() { + @Override + public void update(Observable o, Object 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, + new Comparator<AuthenticationProfile>() { + @Override + public int compare(AuthenticationProfile o1, AuthenticationProfile o2) { + return Integer.valueOf(o1.getNegotiationPriority()).compareTo(o2.getNegotiationPriority()); + } + } + ); + } + + @Override + protected void authenticateWithSSO(HttpServletRequest req, HttpServletResponse resp) throws ServletException { + 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); + } + Map<String, Object> payload = doAuth(req, resp); + + if (!payload.isEmpty()) { + Map<String, Object> response = SSOOAuthServiceUtils.loginOnBehalf(req, resp, payload, scope); + FiltersHelper.isStatusOk(response); + createUserSession(req, FiltersHelper.getPayloadForToken(req, resp, (String) response.get("access_token"))); + } + } catch (Exception e) { + log.error(e.getMessage()); + log.debug("External Authentication with SSO failed", e); + } + } + + public Map<String, Object> doAuth(HttpServletRequest req, HttpServletResponse rsp) + throws ServletException { + Map<String, Object> payload = new HashMap<>(); + log.debug("Performing external authentication"); + Deque<AuthenticationProfile> stack = (Deque<AuthenticationProfile>) req.getSession(true).getAttribute(STACK_ATTR); + if (stack == null) { + stack = new ArrayDeque<>(profiles); + } + 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 + ) + ); + + 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 + )); + stack.clear(); + ExtMap principalRecord = outputMap.get(Authz.InvokeKeys.PRINCIPAL_RECORD); + String principal = principalRecord.get(Authz.PrincipalRecord.PRINCIPAL); + String validTo = authRecord.get(Authn.AuthRecord.VALID_TO); + payload.put("user_id", principal != null ? principal : principalRecord.<String>get(Authz.PrincipalRecord.NAME)); + payload.put("profile", profile.getName()); + payload.put("principal_id", principalRecord.<String>get(Authz.PrincipalRecord.ID)); + payload.put("email", principalRecord.<String>get(Authz.PrincipalRecord.EMAIL)); + payload.put("group_ids", principalRecord.get(Authz.PrincipalRecord.GROUPS, Collections.<ExtMap>emptyList())); + payload.put("valid_to", StringUtils.isEmpty(validTo) ? "" + Integer.MAX_VALUE : validTo); + 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)); + return payload; + } +} diff --git a/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml b/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml index 9afe685..b21fbf0 100644 --- a/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml +++ b/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml @@ -70,11 +70,29 @@ </filter-mapping> <filter> - <filter-name>SSORestApiLoginFilter</filter-name> - <filter-class>org.ovirt.engine.core.aaa.filters.SSORestApiLoginFilter</filter-class> + <filter-name>SSORestApiBearerAuthFilter</filter-name> + <filter-class>org.ovirt.engine.core.aaa.filters.SSORestApiBearerAuthFilter</filter-class> </filter> <filter-mapping> - <filter-name>SSORestApiLoginFilter</filter-name> + <filter-name>SSORestApiBearerAuthFilter</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> + + <filter> + <filter-name>SSORestApiBasicAuthFilter</filter-name> + <filter-class>org.ovirt.engine.core.aaa.filters.SSORestApiBasicAuthFilter</filter-class> + </filter> + <filter-mapping> + <filter-name>SSORestApiBasicAuthFilter</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> + + <filter> + <filter-name>SSORestApiNegotiationFilter</filter-name> + <filter-class>org.ovirt.engine.core.aaa.filters.SSORestApiNegotiationFilter</filter-class> + </filter> + <filter-mapping> + <filter-name>SSORestApiNegotiationFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> -- To view, visit https://gerrit.ovirt.org/42292 To unsubscribe, visit https://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Idee5137430cefa7ca99c047cfd2d550222e5809a Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Ravi Nori <rn...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches