[Lada-commits] [PATCH 03 of 15] Merged branch openid back to default

Wald Commits scm-commit at wald.intevation.org
Mon Mar 23 17:59:55 CET 2015


# HG changeset patch
# User Raimund Renkert <raimund.renkert at intevation.de>
# Date 1426771724 -3600
# Node ID b0d674240c2998c74e50986be8c3f050c4c1602a
# Parent  08084d754073ed880f46e2aab1d4c796cebb6a2e
# Parent  0e46adb8fcc5972c560c963267facac00cd653e8
Merged branch openid back to default.

diff -r 08084d754073 -r b0d674240c29 pom.xml
--- a/pom.xml	Thu Mar 19 09:28:18 2015 +0100
+++ b/pom.xml	Thu Mar 19 14:28:44 2015 +0100
@@ -60,7 +60,11 @@
             <groupId>org.jboss.spec.javax.json</groupId>
             <artifactId>jboss-json-api_1.0_spec</artifactId>
         </dependency>
-
+        <dependency>
+            <groupId>org.jboss.spec.javax.servlet</groupId>
+            <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+            <version>1.0.2.Final</version>
+        </dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-annotations</artifactId>
@@ -139,6 +143,13 @@
             <version>3.0.10.Final</version>
             <scope>test</scope>
         </dependency>
+
+        <!-- OpenID -->
+        <dependency>
+            <groupId>org.openid4java</groupId>
+            <artifactId>openid4java</artifactId>
+            <version>0.9.7</version>
+        </dependency>
     </dependencies>
 
     <profiles>
diff -r 08084d754073 -r b0d674240c29 src/main/java/de/intevation/lada/rest/LoginService.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/intevation/lada/rest/LoginService.java	Thu Mar 19 14:28:44 2015 +0100
@@ -0,0 +1,49 @@
+/* Copyright (C) 2015 by Bundesamt fuer Strahlenschutz
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU GPL (v>=3) 
+ * and comes with ABSOLUTELY NO WARRANTY! Check out 
+ * the documentation coming with IMIS-Labordaten-Application for details. 
+ */
+
+import javax.enterprise.context.RequestScoped;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.GET;
+import javax.inject.Inject;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.Produces;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.lada.util.rest.Response;
+/**
+ * This class serves as a login check service
+ */
+ at Path("login")
+ at RequestScoped
+public class LoginService {
+
+    /* The logger used in this class.*/
+    @Inject
+    private Logger logger;
+
+    /**
+     * Get all probe objects.
+     *
+     * @return Response object containing all probe objects.
+     */
+    @SuppressWarnings("unchecked")
+    @GET
+    @Path("/")
+    @Produces("application/json")
+    public Response get(
+        @Context HttpHeaders headers,
+        @Context UriInfo info
+    ) {
+        /* This should probably contain the users name and roles. */
+        return new Response(true, 200, "Success");
+    }
+}
diff -r 08084d754073 -r b0d674240c29 src/main/java/de/intevation/lada/util/auth/OpenIDFilter.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/intevation/lada/util/auth/OpenIDFilter.java	Thu Mar 19 14:28:44 2015 +0100
@@ -0,0 +1,360 @@
+/* Copyright (C) 2015 by Bundesamt fuer Strahlenschutz
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU GPL (v>=3) 
+ * and comes with ABSOLUTELY NO WARRANTY! Check out 
+ * the documentation coming with IMIS-Labordaten-Application for details. 
+ */
+
+package de.intevation.lada.util.auth;
+
+import org.apache.log4j.Logger;
+
+import java.util.Map;
+import java.util.List;
+import java.util.LinkedHashMap;
+import java.net.URLDecoder;
+import java.util.Date;
+import java.util.Properties;
+import java.util.Enumeration;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+
+import org.openid4java.association.AssociationSessionType;
+import org.openid4java.association.AssociationException;
+import org.openid4java.consumer.ConsumerManager;
+import org.openid4java.consumer.ConsumerException;
+import org.openid4java.consumer.InMemoryConsumerAssociationStore;
+import org.openid4java.consumer.AbstractNonceVerifier;
+import org.openid4java.message.ParameterList;
+import org.openid4java.consumer.VerificationResult;
+import org.openid4java.discovery.DiscoveryInformation;
+import org.openid4java.discovery.Identifier;
+import org.openid4java.discovery.DiscoveryException;
+import org.openid4java.message.MessageException;
+import org.openid4java.message.AuthRequest;
+import org.openid4java.message.AuthSuccess;
+import org.openid4java.message.ax.AxMessage;
+import org.openid4java.message.ax.FetchRequest;
+import org.openid4java.message.ax.FetchResponse;
+
+/** ServletFilter used for OpenID authentification. */
+ at WebFilter("/*")
+public class OpenIDFilter implements Filter {
+
+    private static final String CONFIG_FILE = "/openid.properties";
+
+    /** The name of the header field used to transport OpenID parameters.*/
+    private static final String OID_HEADER_DEFAULT = "X-OPENID-PARAMS";
+    private String oidHeader;
+
+    /** The identity provider we accept here. */
+    private static final String IDENTITY_PROVIDER_DEFAULT =
+        "https://localhost/openid/";
+    private String providerUrl;
+
+    private static final int SESSION_TIMEOUT_DEFAULT_MINUTES = 60;
+    private int sessionTimeout;
+
+    private boolean enabled;
+
+    private static Logger logger = Logger.getLogger(OpenIDFilter.class);
+
+    /** Nonce verifier to allow a session based on openid information.
+     *
+     * Usually one would create a session for the user but this would not
+     * be an advantage here as we want to transport the session in a header
+     * anyway.
+     *
+     * A nonce will be valid as long as as the maxAge is not reached.
+     * This is implemented by the basis verifier.
+     * We only implement seed no mark that we accept nonce's multiple
+     * times.
+     */
+    private class SessionNonceVerifier extends AbstractNonceVerifier {
+        public SessionNonceVerifier(int maxAge) {
+            super(maxAge);
+        }
+
+        @Override
+        protected int seen(Date now, String opUrl, String nonce) {
+            return OK;
+        }
+    };
+
+    private ConsumerManager manager;
+
+    /* This should be moved into a map <server->discovered>
+     * as we currently only supporting one server this is static. */
+    boolean discoveryDone = false;
+    private DiscoveryInformation discovered;
+
+    private boolean discoverServer() {
+        /* Perform discovery on the configured providerUrl */
+        List discoveries = null;
+        try {
+            discoveries = manager.discover(providerUrl);
+        } catch (DiscoveryException e) {
+            logger.debug("Discovery failed: " + e.getMessage());
+            return false;
+        }
+
+        if (discoveries == null || discoveries.isEmpty()) {
+            logger.error(
+                    "Failed discovery step. OpenID provider unavailable?");
+            return false;
+        }
+
+        /* Add association for the discovered information */
+        discovered = manager.associate(discoveries);
+
+        return true;
+    }
+
+    /** Split up the OpenID response query provided in the header.
+     *
+     * @param responseQuery The query provided in the header field.
+     * @return The query as ParameterList or null on error.
+     */
+    private ParameterList splitParams(String responseQuery) {
+        if (responseQuery == null) {
+            return null;
+        }
+        Map<String, String> queryMap =
+            new LinkedHashMap<String, String>();
+        final String[] pairs = responseQuery.split("&");
+        for (String pair : pairs) {
+            final int idx = pair.indexOf("=");
+            if (idx <= 0) {
+                logger.debug("Invalid query.");
+                return null;
+            }
+            try {
+                final String key = URLDecoder.decode(
+                        pair.substring(0, idx), "UTF-8");
+
+                if (queryMap.containsKey(key)) {
+                    logger.debug("Duplicate key: " + key + " ignored.");
+                    continue;
+                }
+                final String value = URLDecoder.decode(
+                        pair.substring(idx + 1), "UTF-8");
+                queryMap.put(key, value);
+            } catch (java.io.UnsupportedEncodingException e) {
+                logger.error("UTF-8 unkown?!");
+                return null;
+            }
+        }
+        if (queryMap.isEmpty()) {
+            logger.debug("Empty query.");
+            return null;
+        }
+        return new ParameterList(queryMap);
+    }
+
+    private boolean checkOpenIDHeader(ServletRequest req) {
+
+        HttpServletRequest hReq = (HttpServletRequest) req;
+
+        /* Debug code to dump headers
+        Enumeration<String> headerNames = hReq.getHeaderNames();
+        while (headerNames.hasMoreElements()) {
+            String headerName = headerNames.nextElement();
+            logger.debug("Header: " + headerName);
+            Enumeration<String> headers = hReq.getHeaders(headerName);
+            while (headers.hasMoreElements()) {
+                String headerValue = headers.nextElement();
+                logger.debug("Value: " + headerValue);
+            }
+        }
+        */
+        /* First check if the header is provided at all */
+        String oidParamString = hReq.getHeader(oidHeader);
+
+        if (oidParamString == null) {
+            logger.debug("Header " + oidHeader + " not provided. Trying params.");
+            oidParamString = hReq.getQueryString();
+        }
+
+        /* Parse the parameters to a map for openid4j */
+        ParameterList oidParams = splitParams(oidParamString);
+        if (oidParams == null) {
+            return false;
+        }
+
+        /* Verify against the discovered server. */
+        VerificationResult verification = null;
+        String receivingURL = oidParams.getParameterValue("openid.return_to");
+
+        try {
+            verification = manager.verify(receivingURL, oidParams,
+                    discovered);
+        } catch (MessageException e) {
+            logger.debug("Verification failed: " + e.getMessage());
+            return false;
+        } catch (DiscoveryException e) {
+            logger.debug("Verification discovery exception: " + e.getMessage());
+            return false;
+        } catch (AssociationException e) {
+            logger.debug("Verification assoc exception: " + e.getMessage());
+            return false;
+        }
+
+        /* See what could be verified */
+        Identifier verified = verification.getVerifiedId();
+        if (verified == null) {
+            logger.debug("Failed to verify Identity information: " +
+                    verification.getStatusMsg());
+            return false;
+        }
+
+        AuthSuccess authSuccess =
+                        (AuthSuccess) verification.getAuthResponse();
+        String rolesValue;
+        if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
+            FetchResponse fetchResp = null;
+            try {
+                fetchResp = (FetchResponse) authSuccess.getExtension(
+                        AxMessage.OPENID_NS_AX);
+            } catch (MessageException e) {
+                logger.debug("Failed to fetch extended result: " +
+                        e.getMessage());
+                return false;
+            }
+            String roles = fetchResp.getAttributeValue("attr1");
+            logger.debug("Roles are: " + roles);
+        } else {
+            logger.debug("No such extension.");
+        }
+
+        logger.debug("Verified user: " + verified);
+
+        return true;
+    }
+
+    @Override
+    public void init(FilterConfig config)
+    throws ServletException
+    {
+        /* Read config and initialize configuration variables */
+        Properties properties = new Properties();
+        InputStream stream = null;
+        try {
+            stream = getClass().getResourceAsStream(CONFIG_FILE);
+            properties.load(stream);
+            stream.close();
+        } catch (java.io.FileNotFoundException e) {
+            logger.error ("Failed to find config file: " + CONFIG_FILE);
+        } catch (java.io.IOException e) {
+            logger.error ("Failed to read config file: " + CONFIG_FILE);
+        }
+        try {
+            sessionTimeout = Integer.parseInt(
+                    properties.getProperty("session_timeout_minutes"));
+        } catch (NumberFormatException e) {
+            sessionTimeout = SESSION_TIMEOUT_DEFAULT_MINUTES;
+        }
+        oidHeader = properties.getProperty("oidHeader", OID_HEADER_DEFAULT);
+        providerUrl = properties.getProperty("identity_provider",
+                IDENTITY_PROVIDER_DEFAULT);
+        enabled = !properties.getProperty("enabled",
+                "true").toLowerCase().equals("false");
+
+        manager = new ConsumerManager();
+        /* We probably want to implement our own association store to keep
+         * associations persistent. */
+        manager.setAssociations(new InMemoryConsumerAssociationStore());
+        manager.setNonceVerifier(new SessionNonceVerifier(sessionTimeout * 60));
+        manager.setMinAssocSessEnc(AssociationSessionType.DH_SHA256);
+        discoveryDone = discoverServer();
+    }
+
+    @Override
+    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
+    throws IOException, ServletException
+    {
+        if (!enabled) {
+            /* If we are not enabled we pass everything through */
+            logger.debug("OpenID filter disabled. Passing through.");
+            chain.doFilter(req, resp);
+            return;
+        }
+
+        HttpServletRequest hReq = (HttpServletRequest) req;
+        HttpServletResponse hResp = (HttpServletResponse) resp;
+        if (!discoveryDone) {
+            discoveryDone = discoverServer();
+        }
+        if (discoveryDone && checkOpenIDHeader(req)) {
+            /** Successfully authenticated. */
+            hResp.addHeader(oidHeader, hReq.getQueryString().replace(
+                        "is_return=true",""));
+            chain.doFilter(req, resp);
+            return;
+        }
+        String authRequestURL = "Error communicating with openid server";
+        int errorCode = 698;
+        if (discoveryDone) {
+            /* Parse the parameters to a map for openid4j */
+            ParameterList params = splitParams(hReq.getQueryString());
+            String returnToUrl;
+            if (params == null) {
+                logger.debug("Failed to get any parameters from url.");
+                hResp.reset();
+                hResp.setStatus(401);
+                hResp.getOutputStream().print("{\"success\":false,\"message\":\"" + errorCode + "\",\"data\":" +
+                        "\"No return url provided!\",\"errors\":{},\"warnings\":{}," +
+                        "\"readonly\":false,\"totalCount\":0}");
+                hResp.getOutputStream().flush();
+                return;
+            } else {
+                returnToUrl = params.getParameterValue("return_to");
+            }
+            try {
+                AuthRequest authReq = manager.authenticate(discovered,
+                        returnToUrl);
+                // Fetch the role attribute
+                FetchRequest fetch = FetchRequest.createFetchRequest();
+
+                fetch.addAttribute("attr1",
+                        "http://axschema.org/person/role",
+                        true, 0);
+                // attach the extension to the authentication request
+                authReq.addExtension(fetch);
+
+                authRequestURL = authReq.getDestinationUrl(true);
+                errorCode = 699;
+            } catch (MessageException e) {
+                logger.debug("Failed to create the Authentication request: " +
+                        e.getMessage());
+            } catch (ConsumerException e) {
+                logger.debug("Error in consumer manager: " +
+                        e.getMessage());
+            }
+        }
+        hResp.reset();
+        hResp.setStatus(401);
+        hResp.getOutputStream().print("{\"success\":false,\"message\":\"" + errorCode + "\",\"data\":" +
+                "\"" + authRequestURL + "\",\"errors\":{},\"warnings\":{}," +
+                "\"readonly\":false,\"totalCount\":0}");
+        hResp.getOutputStream().flush();
+    }
+    @Override
+    public void destroy()
+    {
+    }
+};
diff -r 08084d754073 -r b0d674240c29 src/main/resources/openid.properties
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/resources/openid.properties	Thu Mar 19 14:28:44 2015 +0100
@@ -0,0 +1,14 @@
+# The name of the header field used to transport read OpenID
+# parameters from the client
+oid_header=X-OPENID-PARAMS
+
+# The URL of the identity provder
+identity_provider=https://bfs-identity.intevation.de/openid/
+
+# Session timeout in minutes
+session_timeout_minutes=60
+
+# Set this to false to disable the openID filter altogether
+# doing this will disable authentication and authorization
+# completely. Use this only for testing!
+enabled=true


More information about the Lada-commits mailing list