# HG changeset patch # User Raimund Renkert # 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 @@ org.jboss.spec.javax.json jboss-json-api_1.0_spec - + + org.jboss.spec.javax.servlet + jboss-servlet-api_3.0_spec + 1.0.2.Final + com.fasterxml.jackson.core jackson-annotations @@ -139,6 +143,13 @@ 3.0.10.Final test + + + + org.openid4java + openid4java + 0.9.7 + 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 + */ +@Path("login") +@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. */ +@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 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 queryMap = + new LinkedHashMap(); + 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 headerNames = hReq.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + logger.debug("Header: " + headerName); + Enumeration 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