cloudstack-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bhais...@apache.org
Subject [10/50] git commit: updated refs/heads/master to 97ed5ff
Date Thu, 28 Aug 2014 17:58:14 GMT
SAML2LoginAPIAuthenticatorCmd: Implement SAML SSO using HTTP Redirect binding

- Creates SAMLRequest and uses HTTP redirect binding (uses GET/302)
- Redirects to IdP for auth
- On successful auth, check for assertion
- Tries to get attributes based on standard LDAP attribute names
- Next, gets user using EntityManager, if not found creates one with NameID as UUID
- Finally tries to log in and redirect

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


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

Branch: refs/heads/master
Commit: a1dc9e8189ebdab3f7e8b849f1777f282a7a295b
Parents: 9c7204d
Author: Rohit Yadav <rohit.yadav@shapeblue.com>
Authored: Mon Aug 18 03:43:58 2014 +0200
Committer: Rohit Yadav <rohit.yadav@shapeblue.com>
Committed: Thu Aug 28 19:45:21 2014 +0200

----------------------------------------------------------------------
 .../api/auth/SAML2LoginAPIAuthenticatorCmd.java | 289 ++++++++++---------
 1 file changed, 153 insertions(+), 136 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a1dc9e81/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 c6b0bb6..4e17d3d 100644
--- a/server/src/com/cloud/api/auth/SAML2LoginAPIAuthenticatorCmd.java
+++ b/server/src/com/cloud/api/auth/SAML2LoginAPIAuthenticatorCmd.java
@@ -17,7 +17,13 @@
 
 package com.cloud.api.auth;
 
+import com.cloud.api.ApiServerService;
+import com.cloud.api.response.ApiResponseSerializer;
+import com.cloud.exception.CloudAuthenticationException;
 import com.cloud.user.Account;
+import com.cloud.user.User;
+import com.cloud.utils.HttpUtils;
+import com.cloud.utils.db.EntityManager;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ApiErrorCode;
@@ -25,18 +31,26 @@ import org.apache.cloudstack.api.BaseCmd;
 import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.response.LoginCmdResponse;
+import org.apache.cloudstack.context.CallContext;
 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.Assertion;
+import org.opensaml.saml2.core.Attribute;
+import org.opensaml.saml2.core.AttributeStatement;
 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.NameID;
 import org.opensaml.saml2.core.NameIDPolicy;
+import org.opensaml.saml2.core.NameIDType;
 import org.opensaml.saml2.core.RequestedAuthnContext;
+import org.opensaml.saml2.core.Response;
+import org.opensaml.saml2.core.StatusCode;
 import org.opensaml.saml2.core.impl.AuthnContextClassRefBuilder;
 import org.opensaml.saml2.core.impl.AuthnRequestBuilder;
 import org.opensaml.saml2.core.impl.IssuerBuilder;
@@ -49,15 +63,15 @@ 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.signature.Signature;
 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.inject.Inject;
+import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 import javax.xml.parsers.DocumentBuilder;
@@ -71,6 +85,7 @@ import java.io.StringWriter;
 import java.math.BigInteger;
 import java.net.URLEncoder;
 import java.security.SecureRandom;
+import java.util.List;
 import java.util.Map;
 import java.util.zip.Deflater;
 import java.util.zip.DeflaterOutputStream;
@@ -86,6 +101,11 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements
APIAuthent
     @Parameter(name = ApiConstants.IDP_URL, type = CommandType.STRING, description = "Identity
Provider SSO HTTP-Redirect binding URL", required = true)
     private String idpUrl;
 
+    @Inject
+    ApiServerService _apiServer;
+    @Inject
+    EntityManager _entityMgr;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -114,34 +134,30 @@ 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) {
+    public String buildAuthnRequestUrl(String consumerUrl, String identityProviderUrl) {
         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 = "";
-
+        String spId = "org.apache.cloudstack";
+        String redirectUrl = "";
         try {
             DefaultBootstrap.bootstrap();
-            AuthnRequest authnRequest = this.buildAuthnRequestObject(randomId, identityProviderUrl,
resourceUrl); // SAML AuthRequest
-            encodedAuthRequest = encodeAuthnRequest(authnRequest);
+            AuthnRequest authnRequest = this.buildAuthnRequestObject(randomId, spId, identityProviderUrl,
consumerUrl);
+            redirectUrl = identityProviderUrl + "?SAMLRequest=" + encodeAuthnRequest(authnRequest);
         } catch (ConfigurationException | FactoryConfigurationError | MarshallingException
| IOException e) {
             s_logger.error("SAML AuthnRequest message building error: " + e.getMessage());
         }
-        return identityProviderUrl + "?SAMLRequest=" + encodedAuthRequest; // + "&RelayState="
+ relayState;
+        return redirectUrl;
     }
 
-    private AuthnRequest buildAuthnRequestObject(String authnId, String idpUrl, String consumerUrl)
{
+    private AuthnRequest buildAuthnRequestObject(String authnId, String spId, String idpUrl,
String consumerUrl) {
         // Issuer object
         IssuerBuilder issuerBuilder = new IssuerBuilder();
         Issuer issuer = issuerBuilder.buildObject();
-        //SAMLConstants.SAML20_NS,
-        //        "Issuer", "samlp");
-        issuer.setValue("apache-cloudstack");
+        issuer.setValue(spId);
 
         // NameIDPolicy
         NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder();
         NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject();
-        nameIdPolicy.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent");
+        nameIdPolicy.setFormat(NameIDType.PERSISTENT);
         nameIdPolicy.setSPNameQualifier("Apache CloudStack");
         nameIdPolicy.setAllowCreate(true);
 
@@ -156,16 +172,13 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements
APIAuthent
         RequestedAuthnContextBuilder requestedAuthnContextBuilder = new RequestedAuthnContextBuilder();
         RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
         requestedAuthnContext
-                .setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
+                .setComparison(AuthnContextComparisonTypeEnumeration.MINIMUM);
         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);
@@ -174,154 +187,158 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements
APIAuthent
         authnRequest.setIssuer(issuer);
         authnRequest.setIssueInstant(new DateTime());
         authnRequest.setProviderName("Apache CloudStack");
-        authnRequest.setProtocolBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
+        authnRequest.setProtocolBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); //SAML2_ARTIFACT_BINDING_URI);
         authnRequest.setAssertionConsumerServiceURL(consumerUrl);
-        //authnRequest.setNameIDPolicy(nameIdPolicy);
-        //authnRequest.setRequestedAuthnContext(requestedAuthnContext);
+        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();
+        Marshaller marshaller = Configuration.getMarshallerFactory()
+                .getMarshaller(authnRequest);
+        Element authDOM = marshaller.marshall(authnRequest);
+        StringWriter 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
+        String requestMessage = requestWriter.toString();
+        Deflater deflater = new Deflater(Deflater.DEFLATED, true);
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream,
deflater);
+        deflaterOutputStream.write(requestMessage.getBytes());
         deflaterOutputStream.close();
-
-        encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream
-                .toByteArray(), Base64.DONT_BREAK_LINES);
-        encodedRequestMessage = URLEncoder.encode(encodedRequestMessage,
-                "UTF-8").trim(); // encoding string
-
+        String encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream.toByteArray(),
Base64.DONT_BREAK_LINES);
+        encodedRequestMessage = URLEncoder.encode(encodedRequestMessage, "UTF-8").trim();
         return encodedRequestMessage;
     }
 
-
-    public String processResponseMessage(String responseMessage) {
-
+    public Response processSAMLResponse(String responseMessage) {
         XMLObject responseObject = null;
-
         try {
-
             responseObject = this.unmarshall(responseMessage);
 
         } catch (ConfigurationException | ParserConfigurationException | SAXException | IOException
| UnmarshallingException e) {
-            e.printStackTrace();
+            s_logger.error("SAMLResponse processing error: " + e.getMessage());
         }
-
-        return this.getResult(responseObject);
+        return (Response) 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();
-
+        try {
+            DefaultBootstrap.bootstrap();
+        } catch (ConfigurationException | FactoryConfigurationError e) {
+            s_logger.error("SAML response message decoding error: " + e.getMessage());
+        }
+        DocumentBuilderFactory 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;
+        DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
+        byte[] base64DecodedResponse = Base64.decode(responseMessage);
+        Document document = docBuilder.parse(new ByteArrayInputStream(base64DecodedResponse));
+        Element element = document.getDocumentElement();
+        UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
+        Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
+        return unmarshaller.unmarshall(element);
     }
 
-
-
     @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;
+    public String authenticate(final String command, final Map<String, Object[]> params,
final HttpSession session, final String remoteAddress, final String responseType, final StringBuilder
auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
         try {
-            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
-
+            if (!params.containsKey("SAMLResponse")) {
+                final String[] idps = (String[])params.get("idpurl");
+                String redirectUrl = buildAuthnRequestUrl("http://localhost:8080/client/api?command=samlsso",
idps[0]);
+                resp.sendRedirect(redirectUrl);
+                return "";
+            } else {
+                final String samlResponse = ((String[])params.get("SAMLResponse"))[0];
+                Response processedSAMLResponse = processSAMLResponse(samlResponse);
+                String statusCode = processedSAMLResponse.getStatus().getStatusCode().getValue();
+                if (!statusCode.equals(StatusCode.SUCCESS_URI)) {
+                    throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                            "Identity Provider send a non-successful authentication status
code",
+                            params, responseType));
+                }
+
+                Signature sig = processedSAMLResponse.getSignature();
+                //SignatureValidator validator = new SignatureValidator(credential);
+                //validator.validate(sig);
+
+                String uniqueUserId = null;
+                String accountName = "admin"; //GET from config, try, fail
+                Long domainId = 1L; // GET from config, try, fail
+                String username = null;
+                String password = "";
+                String firstName = "";
+                String lastName = "";
+                String timeZone = "";
+                String email = "";
+
+                Assertion assertion = processedSAMLResponse.getAssertions().get(0);
+                NameID nameId = assertion.getSubject().getNameID();
+
+                if (nameId.getFormat().equals(NameIDType.PERSISTENT) || nameId.getFormat().equals(NameIDType.EMAIL))
{
+                    username = nameId.getValue();
+                    uniqueUserId = "saml-" + username;
+                    if (nameId.getFormat().equals(NameIDType.EMAIL)) {
+                        email = username;
+                    }
+                }
+
+                String issuer = assertion.getIssuer().getValue();
+                String audience = assertion.getConditions().getAudienceRestrictions().get(0).getAudiences().get(0).getAudienceURI();
+                AttributeStatement attributeStatement = assertion.getAttributeStatements().get(0);
+                List<Attribute> attributes = attributeStatement.getAttributes();
+
+                // Try capturing standard LDAP attributes
+                for (Attribute attribute: attributes) {
+                    String attributeName = attribute.getName();
+                    String attributeValue = attribute.getAttributeValues().get(0).getDOM().getTextContent();
+                    if (attributeName.equalsIgnoreCase("uid") && uniqueUserId ==
null) {
+                        username = attributeValue;
+                        uniqueUserId = "saml-" + username;
+                    } else if (attributeName.equalsIgnoreCase("givenName")) {
+                        firstName = attributeValue;
+                    } else if (attributeName.equalsIgnoreCase(("sn"))) {
+                        lastName = attributeValue;
+                    } else if (attributeName.equalsIgnoreCase("mail")) {
+                        email = attributeValue;
+                    }
+                }
+
+                User user = _entityMgr.findByUuid(User.class, uniqueUserId);
+                if (user == null && uniqueUserId != null && username != null
+                        && accountName != null && domainId != null) {
+                    CallContext.current().setEventDetails("UserName: " + username + ", FirstName
:" + password + ", LastName: " + lastName);
+                    user = _accountService.createUser(username, password, firstName, lastName,
email, timeZone, accountName, domainId, uniqueUserId);
+                }
+
+                if (user != null) {
+                    try {
+                        if (_apiServer.verifyUser(user.getId())) {
+                            LoginCmdResponse loginResponse = (LoginCmdResponse) _apiServer.loginUser(session,
username, user.getPassword(), domainId, null, remoteAddress, params);
+                            resp.addCookie(new Cookie("userid", loginResponse.getUserId()));
+                            resp.addCookie(new Cookie("domainid", loginResponse.getDomainId()));
+                            resp.addCookie(new Cookie("role", loginResponse.getType()));
+                            resp.addCookie(new Cookie("username", URLEncoder.encode(loginResponse.getUsername(),
HttpUtils.UTF_8)));
+                            resp.addCookie(new Cookie("sessionKey", URLEncoder.encode(loginResponse.getSessionKey(),
HttpUtils.UTF_8)));
+                            resp.addCookie(new Cookie("account", URLEncoder.encode(loginResponse.getAccount(),
HttpUtils.UTF_8)));
+                            //resp.sendRedirect("http://localhost:8080/client");
+                            return ApiResponseSerializer.toSerializedString(loginResponse,
responseType);
+
+                        }
+                    } catch (final CloudAuthenticationException ignored) {
+                    }
+                }
+            }
         } catch (IOException e) {
             auditTrailSb.append("SP initiated SAML authentication using HTTP redirection
failed:");
             auditTrailSb.append(e.getMessage());
         }
-        return response;
+        throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                "Unable to authenticate or retrieve user while performing SAML based SSO",
+                params, responseType));
     }
 
     @Override


Mime
View raw message