SAML: WIP redirections work now

Signed-off-by: Rohit Yadav <rohit.ya...@shapeblue.com>


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

Branch: refs/heads/master
Commit: b82207e081b79261a274058cdd0323aff9c3be46
Parents: 18ff47e
Author: Rohit Yadav <rohit.ya...@shapeblue.com>
Authored: Sun Aug 17 19:12:00 2014 +0200
Committer: Rohit Yadav <rohit.ya...@shapeblue.com>
Committed: Thu Aug 28 19:45:20 2014 +0200

----------------------------------------------------------------------
 .../api/auth/SAML2LoginAPIAuthenticatorCmd.java | 241 ++++++++++++++++++-
 1 file changed, 239 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b82207e0/server/src/com/cloud/api/auth/SAML2LoginAPIAuthenticatorCmd.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/auth/SAML2LoginAPIAuthenticatorCmd.java 
b/server/src/com/cloud/api/auth/SAML2LoginAPIAuthenticatorCmd.java
index beba4f1..c6b0bb6 100644
--- a/server/src/com/cloud/api/auth/SAML2LoginAPIAuthenticatorCmd.java
+++ b/server/src/com/cloud/api/auth/SAML2LoginAPIAuthenticatorCmd.java
@@ -26,11 +26,54 @@ import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.response.LoginCmdResponse;
 import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+import org.opensaml.Configuration;
+import org.opensaml.DefaultBootstrap;
+import org.opensaml.common.SAMLVersion;
+import org.opensaml.common.xml.SAMLConstants;
+import org.opensaml.saml2.core.AuthnContextClassRef;
+import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration;
+import org.opensaml.saml2.core.AuthnRequest;
+import org.opensaml.saml2.core.Issuer;
+import org.opensaml.saml2.core.NameIDPolicy;
+import org.opensaml.saml2.core.RequestedAuthnContext;
+import org.opensaml.saml2.core.impl.AuthnContextClassRefBuilder;
+import org.opensaml.saml2.core.impl.AuthnRequestBuilder;
+import org.opensaml.saml2.core.impl.IssuerBuilder;
+import org.opensaml.saml2.core.impl.NameIDPolicyBuilder;
+import org.opensaml.saml2.core.impl.RequestedAuthnContextBuilder;
+import org.opensaml.xml.ConfigurationException;
+import org.opensaml.xml.XMLObject;
+import org.opensaml.xml.io.Marshaller;
+import org.opensaml.xml.io.MarshallingException;
+import org.opensaml.xml.io.Unmarshaller;
+import org.opensaml.xml.io.UnmarshallerFactory;
+import org.opensaml.xml.io.UnmarshallingException;
+import org.opensaml.xml.util.Base64;
+import org.opensaml.xml.util.XMLHelper;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
 
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.FactoryConfigurationError;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.StringWriter;
+import java.math.BigInteger;
+import java.net.URLEncoder;
+import java.security.SecureRandom;
 import java.util.Map;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
 
 @APICommand(name = "samlsso", description = "SP initiated SAML Single Sign 
On", requestHasSensitiveInfo = true, responseObject = LoginCmdResponse.class, 
entityType = {})
 public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements 
APIAuthenticator {
@@ -71,12 +114,206 @@ public class SAML2LoginAPIAuthenticatorCmd extends 
BaseCmd implements APIAuthent
         throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is 
an authentication api, cannot be used directly");
     }
 
+    public String buildAuthnRequestUrl(String resourceUrl) {
+        String randomId = new BigInteger(130, new SecureRandom()).toString(32);
+        // TODO: Add method to get this url from metadata
+        String identityProviderUrl = 
"https://idp.ssocircle.com:443/sso/SSORedirect/metaAlias/ssocircle";;
+        String encodedAuthRequest = "";
+
+        try {
+            DefaultBootstrap.bootstrap();
+            AuthnRequest authnRequest = this.buildAuthnRequestObject(randomId, 
identityProviderUrl, resourceUrl); // SAML AuthRequest
+            encodedAuthRequest = encodeAuthnRequest(authnRequest);
+        } catch (ConfigurationException | FactoryConfigurationError | 
MarshallingException | IOException e) {
+            s_logger.error("SAML AuthnRequest message building error: " + 
e.getMessage());
+        }
+        return identityProviderUrl + "?SAMLRequest=" + encodedAuthRequest; // 
+ "&RelayState=" + relayState;
+    }
+
+    private AuthnRequest buildAuthnRequestObject(String authnId, String 
idpUrl, String consumerUrl) {
+        // Issuer object
+        IssuerBuilder issuerBuilder = new IssuerBuilder();
+        Issuer issuer = issuerBuilder.buildObject();
+        //SAMLConstants.SAML20_NS,
+        //        "Issuer", "samlp");
+        issuer.setValue("apache-cloudstack");
+
+        // NameIDPolicy
+        NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder();
+        NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject();
+        
nameIdPolicy.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent");
+        nameIdPolicy.setSPNameQualifier("Apache CloudStack");
+        nameIdPolicy.setAllowCreate(true);
+
+        // AuthnContextClass
+        AuthnContextClassRefBuilder authnContextClassRefBuilder = new 
AuthnContextClassRefBuilder();
+        AuthnContextClassRef authnContextClassRef = 
authnContextClassRefBuilder.buildObject(
+                SAMLConstants.SAML20_NS,
+                "AuthnContextClassRef", "saml");
+        
authnContextClassRef.setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");
+
+        // AuthnContex
+        RequestedAuthnContextBuilder requestedAuthnContextBuilder = new 
RequestedAuthnContextBuilder();
+        RequestedAuthnContext requestedAuthnContext = 
requestedAuthnContextBuilder.buildObject();
+        requestedAuthnContext
+                .setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
+        requestedAuthnContext.getAuthnContextClassRefs().add(
+                authnContextClassRef);
+
+
+        // Creation of AuthRequestObject
+        AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
+        AuthnRequest authnRequest = authRequestBuilder.buildObject();
+        //SAMLConstants.SAML20P_NS,
+        //        "AuthnRequest", "samlp");
+        authnRequest.setID(authnId);
+        authnRequest.setDestination(idpUrl);
+        authnRequest.setVersion(SAMLVersion.VERSION_20);
+        authnRequest.setForceAuthn(true);
+        authnRequest.setIsPassive(false);
+        authnRequest.setIssuer(issuer);
+        authnRequest.setIssueInstant(new DateTime());
+        authnRequest.setProviderName("Apache CloudStack");
+        
authnRequest.setProtocolBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
+        authnRequest.setAssertionConsumerServiceURL(consumerUrl);
+        //authnRequest.setNameIDPolicy(nameIdPolicy);
+        //authnRequest.setRequestedAuthnContext(requestedAuthnContext);
+
+        return authnRequest;
+    }
+
+    private String encodeAuthnRequest(AuthnRequest authnRequest)
+            throws MarshallingException, IOException {
+
+        Marshaller marshaller = null;
+        org.w3c.dom.Element authDOM = null;
+        StringWriter requestWriter = null;
+        String requestMessage = null;
+        Deflater deflater = null;
+        ByteArrayOutputStream byteArrayOutputStream = null;
+        DeflaterOutputStream deflaterOutputStream = null;
+        String encodedRequestMessage = null;
+
+        marshaller = org.opensaml.Configuration.getMarshallerFactory()
+                .getMarshaller(authnRequest); // object to DOM converter
+
+        authDOM = marshaller.marshall(authnRequest); // converting to a DOM
+
+        requestWriter = new StringWriter();
+        XMLHelper.writeNode(authDOM, requestWriter);
+        requestMessage = requestWriter.toString(); // DOM to string
+
+        deflater = new Deflater(Deflater.DEFLATED, true);
+        byteArrayOutputStream = new ByteArrayOutputStream();
+        deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream,
+                deflater);
+        deflaterOutputStream.write(requestMessage.getBytes()); // compressing
+        deflaterOutputStream.close();
+
+        encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream
+                .toByteArray(), Base64.DONT_BREAK_LINES);
+        encodedRequestMessage = URLEncoder.encode(encodedRequestMessage,
+                "UTF-8").trim(); // encoding string
+
+        return encodedRequestMessage;
+    }
+
+
+    public String processResponseMessage(String responseMessage) {
+
+        XMLObject responseObject = null;
+
+        try {
+
+            responseObject = this.unmarshall(responseMessage);
+
+        } catch (ConfigurationException | ParserConfigurationException | 
SAXException | IOException | UnmarshallingException e) {
+            e.printStackTrace();
+        }
+
+        return this.getResult(responseObject);
+    }
+
+    private XMLObject unmarshall(String responseMessage)
+            throws ConfigurationException, ParserConfigurationException,
+            SAXException, IOException, UnmarshallingException {
+
+        DocumentBuilderFactory documentBuilderFactory = null;
+        DocumentBuilder docBuilder = null;
+        Document document = null;
+        Element element = null;
+        UnmarshallerFactory unmarshallerFactory = null;
+        Unmarshaller unmarshaller = null;
+
+        DefaultBootstrap.bootstrap();
+
+        documentBuilderFactory = DocumentBuilderFactory.newInstance();
+
+        documentBuilderFactory.setNamespaceAware(true);
+
+        docBuilder = documentBuilderFactory.newDocumentBuilder();
+
+        document = docBuilder.parse(new ByteArrayInputStream(responseMessage
+                .trim().getBytes())); // response to DOM
+
+        element = document.getDocumentElement(); // the DOM element
+
+        unmarshallerFactory = Configuration.getUnmarshallerFactory();
+
+        unmarshaller = unmarshallerFactory.getUnmarshaller(element);
+
+        return unmarshaller.unmarshall(element); // Response object
+
+    }
+
+    private String getResult(XMLObject responseObject) {
+
+        Element ele = null;
+        NodeList statusNodeList = null;
+        Node statusNode = null;
+        NamedNodeMap statusAttr = null;
+        Node valueAtt = null;
+        String statusValue = null;
+
+        String[] word = null;
+        String result = null;
+
+        NodeList nameIDNodeList = null;
+        Node nameIDNode = null;
+        String nameID = null;
+
+        // reading the Response Object
+        ele = responseObject.getDOM();
+        statusNodeList = ele.getElementsByTagName("samlp:StatusCode");
+        statusNode = statusNodeList.item(0);
+        statusAttr = statusNode.getAttributes();
+        valueAtt = statusAttr.item(0);
+        statusValue = valueAtt.getNodeValue();
+
+        word = statusValue.split(":");
+        result = word[word.length - 1];
+
+        nameIDNodeList = ele.getElementsByTagNameNS(
+                "urn:oasis:names:tc:SAML:2.0:assertion", "NameID");
+        nameIDNode = nameIDNodeList.item(0);
+        nameID = nameIDNode.getFirstChild().getNodeValue();
+
+        result = nameID + ":" + result;
+
+        return result;
+    }
+
+
+
     @Override
     public String authenticate(String command, Map<String, Object[]> params, 
HttpSession session, String remoteAddress, String responseType, StringBuilder 
auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
-
         String response = null;
         try {
-            resp.sendRedirect(getIdpUrl());
+            String redirectUrl = 
buildAuthnRequestUrl("http://localhost:8080/client/api?command=login";);
+            resp.sendRedirect(redirectUrl);
+
+            //resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+            //resp.setHeader("Location", redirectUrl);
 
             // TODO: create and send assertion with the URL as GET params
 

Reply via email to