cxf-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cohei...@apache.org
Subject git commit: Added initial CXF plugin
Date Thu, 31 Jul 2014 14:47:50 GMT
Repository: cxf-fediz
Updated Branches:
  refs/heads/master 60325dc51 -> dc6478468


Added initial CXF plugin


Project: http://git-wip-us.apache.org/repos/asf/cxf-fediz/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf-fediz/commit/dc647846
Tree: http://git-wip-us.apache.org/repos/asf/cxf-fediz/tree/dc647846
Diff: http://git-wip-us.apache.org/repos/asf/cxf-fediz/diff/dc647846

Branch: refs/heads/master
Commit: dc647846870f96d5484909586681670b9d14fcca
Parents: 60325dc
Author: Colm O hEigeartaigh <coheigea@apache.org>
Authored: Thu Jul 31 15:47:19 2014 +0100
Committer: Colm O hEigeartaigh <coheigea@apache.org>
Committed: Thu Jul 31 15:47:19 2014 +0100

----------------------------------------------------------------------
 .../cxf/fediz/core/samlsso/ResponseState.java   |  39 +++
 plugins/cxf/pom.xml                             |  17 +
 .../plugin/AbstractServiceProviderFilter.java   | 318 +++++++++++++++++++
 .../cxf/fediz/cxf/plugin/CXFFedizPrincipal.java |  56 ++++
 .../fediz/cxf/plugin/FedizSecurityContext.java  |  61 ++++
 .../cxf/fediz/cxf/plugin/Messages.properties    |  33 ++
 .../cxf/plugin/SamlRedirectBindingFilter.java   | 265 ++++++++++++++++
 7 files changed, 789 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/dc647846/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/ResponseState.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/ResponseState.java
b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/ResponseState.java
index dfbf9ff..a959f4c 100644
--- a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/ResponseState.java
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/ResponseState.java
@@ -19,11 +19,14 @@
 package org.apache.cxf.fediz.core.samlsso;
 
 import java.io.Serializable;
+import java.util.List;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlRootElement;
 
+import org.apache.cxf.fediz.core.Claim;
+
 @XmlRootElement
 @XmlAccessorType(XmlAccessType.FIELD)
 public class ResponseState implements Serializable {
@@ -36,6 +39,10 @@ public class ResponseState implements Serializable {
     private String webAppDomain;
     private long createdAt;
     private long expiresAt;
+    private List<String> roles;
+    private String issuer;
+    private List<Claim> claims;
+    private String subject;
     
     public ResponseState() {
         
@@ -78,4 +85,36 @@ public class ResponseState implements Serializable {
     public String getAssertion() {
         return assertion;
     }
+
+    public List<String> getRoles() {
+        return roles;
+    }
+
+    public void setRoles(List<String> roles) {
+        this.roles = roles;
+    }
+
+    public List<Claim> getClaims() {
+        return claims;
+    }
+
+    public void setClaims(List<Claim> claims) {
+        this.claims = claims;
+    }
+
+    public String getIssuer() {
+        return issuer;
+    }
+
+    public void setIssuer(String issuer) {
+        this.issuer = issuer;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/dc647846/plugins/cxf/pom.xml
----------------------------------------------------------------------
diff --git a/plugins/cxf/pom.xml b/plugins/cxf/pom.xml
index e7a2ca6..6df62e6 100644
--- a/plugins/cxf/pom.xml
+++ b/plugins/cxf/pom.xml
@@ -64,6 +64,23 @@
         </dependency>
     </dependencies>
     <build>
+        <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
+        <resources>
+            <resource>
+                <directory>src/main/java</directory>
+                <excludes>
+                    <exclude>**/*.java</exclude>
+                </excludes>
+            </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+            </resource>
+        </resources>
+        
+        
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/dc647846/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/AbstractServiceProviderFilter.java
----------------------------------------------------------------------
diff --git a/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/AbstractServiceProviderFilter.java
b/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/AbstractServiceProviderFilter.java
new file mode 100644
index 0000000..8b90f7d
--- /dev/null
+++ b/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/AbstractServiceProviderFilter.java
@@ -0,0 +1,318 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.fediz.cxf.plugin;
+
+import java.io.File;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Date;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Priority;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.PreMatching;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
+import javax.xml.bind.JAXBException;
+
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.i18n.BundleUtils;
+import org.apache.cxf.fediz.core.SAMLSSOConstants;
+import org.apache.cxf.fediz.core.SecurityTokenThreadLocal;
+import org.apache.cxf.fediz.core.config.FedizConfigurator;
+import org.apache.cxf.fediz.core.config.FedizContext;
+import org.apache.cxf.fediz.core.config.SAMLProtocol;
+import org.apache.cxf.fediz.core.samlsso.ResponseState;
+import org.apache.cxf.fediz.core.util.DOMUtils;
+import org.apache.cxf.jaxrs.impl.HttpHeadersImpl;
+import org.apache.cxf.jaxrs.impl.UriInfoImpl;
+import org.apache.cxf.jaxrs.utils.HttpUtils;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.security.SecurityContext;
+import org.apache.cxf.staxutils.StaxUtils;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.saml.SamlAssertionWrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@PreMatching
+@Priority(Priorities.AUTHENTICATION + 1)
+public abstract class AbstractServiceProviderFilter implements ContainerRequestFilter {
+    
+    public static final String SECURITY_CONTEXT_TOKEN = 
+        "org.apache.fediz.SECURITY_TOKEN";
+    protected static final ResourceBundle BUNDLE = 
+        BundleUtils.getBundle(AbstractServiceProviderFilter.class);
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractServiceProviderFilter.class);
+    
+    private String webAppDomain;
+    // private boolean addWebAppContext = true;
+    // private boolean addEndpointAddressToContext;
+    
+    private FedizConfigurator configurator;
+    private String configFile;
+    
+    public String getConfigFile() {
+        return configFile;
+    }
+
+    public void setConfigFile(String configFile) {
+        this.configFile = configFile;
+    }
+    
+    @PostConstruct
+    public synchronized void configure() throws JAXBException, MalformedURLException {
+        if (configurator == null) {
+            try {
+                File f = new File(configFile);
+                if (!f.exists()) {
+                    URL url = ClassLoaderUtils.getResource(configFile, 
+                                                           AbstractServiceProviderFilter.class);
+                    if (url == null) {
+                        url = new URL(configFile);
+                    }
+                    if (url != null) {
+                        f = new File(url.getPath());
+                    }
+                }
+                configurator = new FedizConfigurator();
+                configurator.loadConfig(f);
+                LOG.debug("Fediz configuration read from " + f.getAbsolutePath());
+            } catch (JAXBException e) {
+                LOG.error("Error in parsing configuration", e);
+                throw e;
+            } catch (MalformedURLException e) {
+                LOG.error("Error in loading configuration file", e);
+                throw e;
+            }
+        }
+    }
+    
+    protected boolean checkSecurityContext(Message m) {
+        HttpHeaders headers = new HttpHeadersImpl(m);
+        Map<String, Cookie> cookies = headers.getCookies();
+        
+        Cookie securityContextCookie = cookies.get(SECURITY_CONTEXT_TOKEN);
+        
+        ResponseState responseState = getValidResponseState(securityContextCookie, m);
+        if (responseState == null) {
+            return false;    
+        }
+        
+        Cookie relayStateCookie = cookies.get(SAMLSSOConstants.RELAY_STATE);
+        if (relayStateCookie == null) {
+            reportError("MISSING_RELAY_COOKIE");
+            return false;
+        }
+        String originalRelayState = responseState.getRelayState();
+        if (!originalRelayState.equals(relayStateCookie.getValue())) {
+            // perhaps the response state should also be removed
+            reportError("INVALID_RELAY_STATE");
+            return false;
+        }
+        
+        // Create SecurityContext
+        try {
+            SamlAssertionWrapper assertionWrapper = 
+                new SamlAssertionWrapper(
+                    StaxUtils.read(new StringReader(responseState.getAssertion())).getDocumentElement());
+            setSecurityContext(responseState, m, assertionWrapper);
+        } catch (Exception ex) {
+            reportError("INVALID_RESPONSE_STATE");
+            return false;
+        }
+        
+        return true;
+    }
+    
+    protected void setSecurityContext(
+        ResponseState responseState, Message m, SamlAssertionWrapper assertionWrapper
+    ) throws WSSecurityException {
+        CXFFedizPrincipal principal = 
+            new CXFFedizPrincipal(responseState.getSubject(), responseState.getClaims(),

+                              assertionWrapper.toDOM(DOMUtils.createDocument()));
+        
+        SecurityTokenThreadLocal.setToken(principal.getLoginToken());
+        FedizSecurityContext context = 
+            new FedizSecurityContext(principal, responseState.getRoles());
+        m.put(SecurityContext.class, context);
+    }
+    
+    protected ResponseState getValidResponseState(Cookie securityContextCookie, 
+                                                  Message m) {
+        if (securityContextCookie == null) {
+            // most likely it means that the user has not been offered
+            // a chance to get logged on yet, though it might be that the browser
+            // has removed an expired cookie from its cache; warning is too noisy in the
+            // former case
+            reportTrace("MISSING_RESPONSE_STATE");
+            return null;
+        }
+        String contextKey = securityContextCookie.getValue();
+        
+        FedizContext fedizConfig = getFedizContext(m);
+        SAMLProtocol protocol = (SAMLProtocol)fedizConfig.getProtocol();
+        ResponseState responseState = protocol.getStateManager().getResponseState(contextKey);
+        
+        if (responseState == null) {
+            reportError("MISSING_RESPONSE_STATE");
+            return null;
+        }
+        if (isStateExpired(responseState.getCreatedAt(), responseState.getExpiresAt(), fedizConfig))
{
+            reportError("EXPIRED_RESPONSE_STATE");
+            protocol.getStateManager().removeResponseState(contextKey);
+            return null;
+        }
+        // TODO String webAppContext = getWebAppContext(m);
+        if (webAppDomain != null 
+            && (responseState.getWebAppDomain() == null 
+                || !webAppDomain.equals(responseState.getWebAppDomain()))) {
+            // TODO || responseState.getWebAppContext() == null
+            // TODO || !webAppContext.equals(responseState.getWebAppContext())) {
+            protocol.getStateManager().removeResponseState(contextKey);
+            reportError("INVALID_RESPONSE_STATE");
+            return null;
+        }
+        if (responseState.getAssertion() == null) {
+            reportError("INVALID_RESPONSE_STATE");
+            return null;
+        }
+        return responseState;
+    }
+    
+    protected String createCookie(String name, 
+                                  String value, 
+                                  String path,
+                                  String domain,
+                                  long stateTimeToLive) { 
+        
+        String contextCookie = name + "=" + value;
+        // Setting a specific path restricts the browsers
+        // to return a cookie only to the web applications
+        // listening on that specific context path
+        if (path != null) {
+            contextCookie += ";Path=" + path;
+        }
+        
+        // Setting a specific domain further restricts the browsers
+        // to return a cookie only to the web applications
+        // listening on the specific context path within a particular domain
+        if (domain != null) {
+            contextCookie += ";Domain=" + domain;
+        }
+        
+        // Keep the cookie across the browser restarts until it actually expires.
+        // Note that the Expires property has been deprecated but apparently is 
+        // supported better than 'max-age' property by different browsers 
+        // (Firefox, IE, etc)
+        Date expiresDate = new Date(System.currentTimeMillis() + stateTimeToLive);
+        String cookieExpires = HttpUtils.getHttpDateFormat().format(expiresDate);
+        contextCookie += ";Expires=" + cookieExpires;
+        //TODO: Consider adding an 'HttpOnly' attribute        
+        
+        return contextCookie;
+    }
+    
+    protected boolean isStateExpired(long stateCreatedAt, long expiresAt, FedizContext fedizConfig)
{
+        Date currentTime = new Date();
+        long stateTimeToLive = ((SAMLProtocol)fedizConfig.getProtocol()).getStateTimeToLive();
+        if (currentTime.after(new Date(stateCreatedAt + stateTimeToLive))) {
+            return true;
+        }
+        
+        if (expiresAt > 0 && currentTime.after(new Date(expiresAt))) {
+            return true;
+        }
+        
+        return false;
+    }
+    
+    protected FedizContext getFedizContext(Message message) {
+        String contextName = new UriInfoImpl(message).getRequestUri().getPath();
+        if (contextName == null || contextName.isEmpty()) {
+            contextName = "/";
+        }
+        String[] contextPath = contextName.split("/");
+        if (contextPath.length > 0) {
+            contextName = "/" + contextPath[1];
+        }
+        return getContextConfiguration(contextName);
+    }
+    
+    protected FedizContext getContextConfiguration(String contextName) {
+        if (configurator == null) {
+            throw new IllegalStateException("No Fediz configuration available");
+        }
+        FedizContext config = configurator.getFedizContext(contextName);
+        if (config == null) {
+            throw new IllegalStateException("No Fediz configuration for context :" + contextName);
+        }
+        String catalinaBase = System.getProperty("catalina.base");
+        if (catalinaBase != null && catalinaBase.length() > 0) {
+            config.setRelativePath(catalinaBase);
+        }
+        return config;
+    }
+    
+    protected void reportError(String code) {
+        org.apache.cxf.common.i18n.Message errorMsg = 
+            new org.apache.cxf.common.i18n.Message(code, BUNDLE);
+        LOG.warn(errorMsg.toString());
+    }
+    
+    protected void reportTrace(String code) {
+        if (LOG.isDebugEnabled()) {
+            org.apache.cxf.common.i18n.Message errorMsg = 
+                new org.apache.cxf.common.i18n.Message(code, BUNDLE);
+            LOG.debug(errorMsg.toString());
+        }
+    }
+/*
+ * TODO
+    private String getWebAppContext(Message m) {
+        if (addWebAppContext) {
+            if (addEndpointAddressToContext) {
+                return new UriInfoImpl(m).getBaseUri().getRawPath();
+            } else {
+                String httpBasePath = (String)m.get("http.base.path");
+                return URI.create(httpBasePath).getRawPath();
+            }
+        } else {
+            return "/";
+        }
+    }
+  */  
+    public String getWebAppDomain() {
+        return webAppDomain;
+    }
+
+    public void setWebAppDomain(String webAppDomain) {
+        this.webAppDomain = webAppDomain;
+    }
+/*
+    public void setAddWebAppContext(boolean addWebAppContext) {
+        this.addWebAppContext = addWebAppContext;
+    }
+    */
+        
+}

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/dc647846/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/CXFFedizPrincipal.java
----------------------------------------------------------------------
diff --git a/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/CXFFedizPrincipal.java
b/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/CXFFedizPrincipal.java
new file mode 100644
index 0000000..787883f
--- /dev/null
+++ b/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/CXFFedizPrincipal.java
@@ -0,0 +1,56 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.fediz.cxf.plugin;
+
+import java.util.List;
+
+import org.w3c.dom.Element;
+import org.apache.cxf.fediz.core.Claim;
+import org.apache.cxf.fediz.core.ClaimCollection;
+import org.apache.cxf.fediz.core.FedizPrincipal;
+
+public class CXFFedizPrincipal implements FedizPrincipal {
+    
+    private final String subject;
+    private final List<Claim> claims;
+    private final Element token;
+    
+    public CXFFedizPrincipal(String subject, List<Claim> claims, Element token) {
+        this.subject = subject;
+        this.claims = claims;
+        this.token = token;
+    }
+
+    @Override
+    public String getName() {
+        return subject;
+    }
+
+    @Override
+    public ClaimCollection getClaims() {
+        return new ClaimCollection(claims);
+    }
+
+    @Override
+    public Element getLoginToken() {
+        return token;
+    }
+    
+        
+}

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/dc647846/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/FedizSecurityContext.java
----------------------------------------------------------------------
diff --git a/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/FedizSecurityContext.java
b/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/FedizSecurityContext.java
new file mode 100644
index 0000000..d20aa71
--- /dev/null
+++ b/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/FedizSecurityContext.java
@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.fediz.cxf.plugin;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.cxf.common.security.SimplePrincipal;
+import org.apache.cxf.security.SecurityContext;
+
+public class FedizSecurityContext implements SecurityContext {
+    
+    private Principal principal;
+    private Set<Principal> roles;
+
+    public FedizSecurityContext(Principal principal,
+                                Collection<String> roles) {
+        this.principal = principal;
+        this.roles = new HashSet<Principal>();
+        if (roles != null) {
+            for (String role : roles) {
+                this.roles.add(new SimplePrincipal(role));
+            }
+        }
+    }
+    
+    @Override
+    public Principal getUserPrincipal() {
+        return principal;
+    }
+
+    @Override
+    public boolean isUserInRole(String role) {
+        for (Principal p : roles) {
+            if (p.getName().equals(role)) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/dc647846/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/Messages.properties
----------------------------------------------------------------------
diff --git a/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/Messages.properties
b/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/Messages.properties
new file mode 100644
index 0000000..13c3d92
--- /dev/null
+++ b/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/Messages.properties
@@ -0,0 +1,33 @@
+#
+#
+#    Licensed to the Apache Software Foundation (ASF) under one
+#    or more contributor license agreements. See the NOTICE file
+#    distributed with this work for additional information
+#    regarding copyright ownership. The ASF licenses this file
+#    to you under the Apache License, Version 2.0 (the
+#    "License"); you may not use this file except in compliance
+#    with the License. You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing,
+#    software distributed under the License is distributed on an
+#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#    KIND, either express or implied. See the License for the
+#    specific language governing permissions and limitations
+#    under the License.
+#
+#
+MISSING_TARGET_URI=Target URI is missing
+INVALID_TARGET_URI=Target URI is invalid
+MISSING_REQUEST_STATE=Request State is not available
+EXPIRED_REQUEST_STATE=Request State has expired
+MISSING_SAML_RESPONSE=SamlResponse parameter is missing
+INVALID_SAML_RESPONSE=SamlResponse parameter is invalid
+MISSING_ASSERTION_SERVICE_URL=RequestAssertionConsumerService URI is not set
+MISSING_RESPONSE_STATE=Response State is not available
+INVALID_RESPONSE_STATE=Response State is invalid
+EXPIRED_RESPONSE_STATE=Response State has expired
+MISSING_RELAY_STATE=RelayState is missing
+MISSING_RELAY_COOKIE=RelayState cookie is not available
+INVALID_RELAY_STATE=RelayState is invalid

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/dc647846/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/SamlRedirectBindingFilter.java
----------------------------------------------------------------------
diff --git a/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/SamlRedirectBindingFilter.java
b/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/SamlRedirectBindingFilter.java
new file mode 100644
index 0000000..a456f21
--- /dev/null
+++ b/plugins/cxf/src/main/java/org/apache/cxf/fediz/cxf/plugin/SamlRedirectBindingFilter.java
@@ -0,0 +1,265 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.fediz.cxf.plugin;
+
+import java.net.URI;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+
+import org.apache.cxf.fediz.core.FederationConstants;
+import org.apache.cxf.fediz.core.SAMLSSOConstants;
+import org.apache.cxf.fediz.core.config.FederationProtocol;
+import org.apache.cxf.fediz.core.config.FedizContext;
+import org.apache.cxf.fediz.core.config.SAMLProtocol;
+import org.apache.cxf.fediz.core.exception.ProcessingException;
+import org.apache.cxf.fediz.core.processor.FedizProcessor;
+import org.apache.cxf.fediz.core.processor.FedizProcessorFactory;
+import org.apache.cxf.fediz.core.processor.FedizRequest;
+import org.apache.cxf.fediz.core.processor.FedizResponse;
+import org.apache.cxf.fediz.core.processor.RedirectionResponse;
+import org.apache.cxf.fediz.core.samlsso.ResponseState;
+import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.jaxrs.impl.UriInfoImpl;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.Message;
+import org.apache.wss4j.common.util.DOM2Writer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SamlRedirectBindingFilter extends AbstractServiceProviderFilter {
+    
+    private static final Logger LOG = LoggerFactory.getLogger(SamlRedirectBindingFilter.class);
+    
+    @Context 
+    private MessageContext messageContext;
+    
+    public void filter(ContainerRequestContext context) {
+        Message m = JAXRSUtils.getCurrentMessage();
+        if (checkSecurityContext(m)) {
+            return;
+        } else {
+            try {
+                FedizContext fedConfig = getFedizContext(m);
+                if (isSignInRequired(context, fedConfig)) {
+                    // Unauthenticated -> redirect
+                    FedizProcessor processor = 
+                        FedizProcessorFactory.newFedizProcessor(fedConfig.getProtocol());
+
+                    HttpServletRequest request = messageContext.getHttpServletRequest();
+                    RedirectionResponse redirectionResponse = 
+                        processor.createSignInRequest(request, fedConfig);
+                    String redirectURL = redirectionResponse.getRedirectionURL();
+                    if (redirectURL != null) {
+                        ResponseBuilder response = Response.seeOther(new URI(redirectURL));
+                        Map<String, String> headers = redirectionResponse.getHeaders();
+                        if (!headers.isEmpty()) {
+                            for (String headerName : headers.keySet()) {
+                                response.header(headerName, headers.get(headerName));
+                            }
+                        }
+
+                        context.abortWith(response.build());
+                    } else {
+                        LOG.warn("Failed to create SignInRequest.");
+                        throw ExceptionUtils.toInternalServerErrorException(null, null);
+                    }
+                } else if (isSignInRequest(context, fedConfig)) {
+                    String responseToken = getResponseToken(context, fedConfig);
+                    
+                    if (responseToken == null) {
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("SignIn request must contain a response token from
the IdP");
+                        }
+                        throw ExceptionUtils.toBadRequestException(null, null);
+                    } else {
+                        // processSignInRequest
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("Process SignIn request");
+                            LOG.debug("token=\n" + responseToken);
+                        }
+
+                        FedizRequest wfReq = new FedizRequest();
+                        MultivaluedMap<String, String> params = context.getUriInfo().getQueryParameters();
+                        wfReq.setAction(params.getFirst(FederationConstants.PARAM_ACTION));
+                        wfReq.setResponseToken(responseToken);
+                        wfReq.setState(params.getFirst("RelayState"));
+                        HttpServletRequest request = messageContext.getHttpServletRequest();
+                        wfReq.setRequest(request);
+                        
+                        X509Certificate certs[] = 
+                            (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
+                        wfReq.setCerts(certs);
+
+                        FedizProcessor wfProc = 
+                            FedizProcessorFactory.newFedizProcessor(fedConfig.getProtocol());
+                        FedizResponse wfRes = null;
+                        try {
+                            wfRes = wfProc.processRequest(wfReq, fedConfig);
+                        } catch (ProcessingException ex) {
+                            LOG.error("Federation processing failed: " + ex.getMessage());
+                            throw ExceptionUtils.toNotAuthorizedException(ex, null);
+                        }
+                        
+                        // Validate AudienceRestriction
+                        List<String> audienceURIs = fedConfig.getAudienceUris();
+                        validateAudienceRestrictions(wfRes, audienceURIs, request);
+
+                        // Set the security context
+                        String securityContextKey = UUID.randomUUID().toString();
+                           
+                        SAMLProtocol protocol = (SAMLProtocol)fedConfig.getProtocol();
+                        
+                        long currentTime = System.currentTimeMillis();
+                        Date notOnOrAfter =  wfRes.getTokenExpires();
+                        long expiresAt = 0;
+                        if (notOnOrAfter != null) {
+                            expiresAt = notOnOrAfter.getTime();
+                        } else {
+                            expiresAt = currentTime + protocol.getStateTimeToLive();
+                        }
+                           
+                        String webAppDomain = protocol.getWebAppDomain();
+                        String token = DOM2Writer.nodeToString(wfRes.getToken());
+                        List<String> roles = wfRes.getRoles();
+                        if (roles == null || roles.size() == 0) {
+                            roles = Collections.singletonList("Authenticated");
+                        }
+                        
+                        ResponseState responseState = 
+                            new ResponseState(token, // TODO
+                                              params.getFirst("RelayState"), 
+                                              null, // TODO
+                                              webAppDomain,
+                                              currentTime, 
+                                              expiresAt);
+                        responseState.setClaims(wfRes.getClaims());
+                        responseState.setRoles(roles);
+                        responseState.setIssuer(wfRes.getIssuer());
+                        responseState.setSubject(wfRes.getUsername());
+                        protocol.getStateManager().setResponseState(securityContextKey, responseState);
+                           
+                        long stateTimeToLive = protocol.getStateTimeToLive();
+                        String contextCookie = createCookie(SECURITY_CONTEXT_TOKEN,
+                                                            securityContextKey,
+                                                            null, // TODO
+                                                            webAppDomain,
+                                                            stateTimeToLive);
+                        
+                        // Redirect with cookie set
+                        ResponseBuilder response = 
+                            Response.seeOther(new UriInfoImpl(m).getAbsolutePath());
+                        response.header("Set-Cookie", contextCookie);
+
+                        context.abortWith(response.build());
+                    }
+                    
+                } else {
+                    LOG.error("SignIn parameter is incorrect or not supported");
+                    throw ExceptionUtils.toBadRequestException(null, null);
+                }
+            } catch (Exception ex) {
+                LOG.debug(ex.getMessage(), ex);
+                throw ExceptionUtils.toInternalServerErrorException(ex, null);
+            }
+        }
+    }
+    
+    private boolean isSignInRequired(ContainerRequestContext context, FedizContext fedConfig)
{
+        
+        MultivaluedMap<String, String> params = context.getUriInfo().getQueryParameters();
+        if (fedConfig.getProtocol() instanceof FederationProtocol
+            && params.getFirst(FederationConstants.PARAM_ACTION) == null) {
+            return true;
+        } else if (fedConfig.getProtocol() instanceof SAMLProtocol
+            && params.getFirst(SAMLSSOConstants.RELAY_STATE) == null) {
+            return true;
+        }
+        
+        return false;
+    }
+    
+    private boolean isSignInRequest(ContainerRequestContext context, FedizContext fedConfig)
{
+        
+        MultivaluedMap<String, String> params = context.getUriInfo().getQueryParameters();
+        if (fedConfig.getProtocol() instanceof FederationProtocol
+            && FederationConstants.ACTION_SIGNIN.equals(
+                params.getFirst(FederationConstants.PARAM_ACTION))) {
+            return true;
+        } else if (fedConfig.getProtocol() instanceof SAMLProtocol
+            && params.getFirst(SAMLSSOConstants.RELAY_STATE) != null) {
+            return true;
+        }
+        
+        return false;
+    }
+    
+    private String getResponseToken(ContainerRequestContext context, FedizContext fedConfig)
{
+        
+        MultivaluedMap<String, String> params = context.getUriInfo().getQueryParameters();
+        if (fedConfig.getProtocol() instanceof FederationProtocol) {
+            return params.getFirst(FederationConstants.PARAM_RESULT);
+        } else if (fedConfig.getProtocol() instanceof SAMLProtocol) {
+            return params.getFirst(SAMLSSOConstants.SAML_RESPONSE);
+        }
+        
+        return null;
+    }
+    
+    private void validateAudienceRestrictions(
+        FedizResponse wfRes, 
+        List<String> audienceURIs,
+        HttpServletRequest request
+    ) {
+        // Validate the AudienceRestriction in Security Token (e.g. SAML) 
+        // against the configured list of audienceURIs
+        if (wfRes.getAudience() != null) {
+            boolean validAudience = false;
+            for (String a : audienceURIs) {
+                if (wfRes.getAudience().startsWith(a)) {
+                    validAudience = true;
+                    break;
+                }
+            }
+            
+            if (!validAudience) {
+                LOG.warn("Token AudienceRestriction [" + wfRes.getAudience()
+                         + "] doesn't match with specified list of URIs.");
+                throw ExceptionUtils.toForbiddenException(null, null);
+            }
+            
+            if (LOG.isDebugEnabled() && request.getRequestURL().indexOf(wfRes.getAudience())
== -1) {
+                LOG.debug("Token AudienceRestriction doesn't match with request URL ["
+                        + wfRes.getAudience() + "]  ["
+                        + request.getRequestURL() + "]");
+            }
+        }
+    }
+}


Mime
View raw message