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

Reply via email to