You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by bh...@apache.org on 2014/08/18 11:43:37 UTC
[12/22] git commit: updated refs/heads/saml2 to 54986d5
SAML: WIP redirections work now
Signed-off-by: Rohit Yadav <ro...@shapeblue.com>
Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/40abe1df
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/40abe1df
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/40abe1df
Branch: refs/heads/saml2
Commit: 40abe1df114052b8637e13319c34c1bb38a928ca
Parents: 4563cc6
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Sun Aug 17 19:12:00 2014 +0200
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Mon Aug 18 11:42:34 2014 +0200
----------------------------------------------------------------------
.../api/auth/SAML2LoginAPIAuthenticatorCmd.java | 241 ++++++++++++++++++-
1 file changed, 239 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/40abe1df/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