saml2: Fix plugin after refactoring - Use opensaml version from root pom - Add utils and api as explicit dependency - Add org.apache.cloudstack.saml.SAML2AuthServiceImpl bean - Fix imports in all source files and resource xmls - Use methods available from SAMLUtils to encode/decode SAML request/response - SAML logout api is not the global logout api
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/d45b3035 Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/d45b3035 Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/d45b3035 Branch: refs/heads/master Commit: d45b303569af77c3c8c673bfb47ad6fdf21a8cbc Parents: 591a686 Author: Rohit Yadav <rohit.ya...@shapeblue.com> Authored: Sun Aug 24 16:01:22 2014 +0200 Committer: Rohit Yadav <rohit.ya...@shapeblue.com> Committed: Thu Aug 28 19:45:23 2014 +0200 ---------------------------------------------------------------------- plugins/user-authenticators/saml2/pom.xml | 12 +- .../cloudstack/saml2/spring-saml2-context.xml | 6 +- .../command/SAML2LoginAPIAuthenticatorCmd.java | 128 ++----------------- .../command/SAML2LogoutAPIAuthenticatorCmd.java | 2 +- .../cloudstack/saml/SAML2UserAuthenticator.java | 3 +- 5 files changed, 27 insertions(+), 124 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d45b3035/plugins/user-authenticators/saml2/pom.xml ---------------------------------------------------------------------- diff --git a/plugins/user-authenticators/saml2/pom.xml b/plugins/user-authenticators/saml2/pom.xml index a220dcf..df6aa45 100644 --- a/plugins/user-authenticators/saml2/pom.xml +++ b/plugins/user-authenticators/saml2/pom.xml @@ -35,7 +35,17 @@ <dependency> <groupId>org.opensaml</groupId> <artifactId>opensaml</artifactId> - <version>2.6.1</version> + <version>${cs.opensaml.version}</version> + </dependency> + <dependency> + <groupId>org.apache.cloudstack</groupId> + <artifactId>cloud-utils</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.cloudstack</groupId> + <artifactId>cloud-api</artifactId> + <version>${project.version}</version> </dependency> </dependencies> </project> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d45b3035/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml ---------------------------------------------------------------------- diff --git a/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml b/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml index f244292..15e085d 100644 --- a/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml +++ b/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml @@ -25,8 +25,12 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> - <bean id="SAML2UserAuthenticator" class="org.apache.cloudstack.SAML2UserAuthenticator"> + <bean id="SAML2UserAuthenticator" class="org.apache.cloudstack.saml.SAML2UserAuthenticator"> <property name="name" value="SAML2"/> </bean> + <bean id="SAML2Manager" class="org.apache.cloudstack.saml.SAML2AuthServiceImpl"> + <property name="name" value="SAML2Auth"/> + </bean> + </beans> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d45b3035/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java ---------------------------------------------------------------------- diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java index 611c69b..463df7d 100644 --- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java +++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java @@ -34,63 +34,35 @@ import org.apache.cloudstack.api.auth.APIAuthenticationType; import org.apache.cloudstack.api.auth.APIAuthenticator; import org.apache.cloudstack.api.response.LoginCmdResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.utils.auth.SAMLUtils; 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; -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.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.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; -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.List; 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 { @@ -142,107 +114,23 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent String redirectUrl = ""; try { DefaultBootstrap.bootstrap(); - AuthnRequest authnRequest = this.buildAuthnRequestObject(randomId, spId, identityProviderUrl, consumerUrl); - redirectUrl = identityProviderUrl + "?SAMLRequest=" + encodeAuthnRequest(authnRequest); + AuthnRequest authnRequest = SAMLUtils.buildAuthnRequestObject(randomId, spId, identityProviderUrl, consumerUrl); + redirectUrl = identityProviderUrl + "?SAMLRequest=" + SAMLUtils.encodeSAMLRequest(authnRequest); } catch (ConfigurationException | FactoryConfigurationError | MarshallingException | IOException e) { s_logger.error("SAML AuthnRequest message building error: " + e.getMessage()); } return redirectUrl; } - private AuthnRequest buildAuthnRequestObject(String authnId, String spId, String idpUrl, String consumerUrl) { - // Issuer object - IssuerBuilder issuerBuilder = new IssuerBuilder(); - Issuer issuer = issuerBuilder.buildObject(); - issuer.setValue(spId); - - // NameIDPolicy - NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder(); - NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject(); - nameIdPolicy.setFormat(NameIDType.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.MINIMUM); - requestedAuthnContext.getAuthnContextClassRefs().add( - authnContextClassRef); - - // Creation of AuthRequestObject - AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder(); - AuthnRequest authnRequest = authRequestBuilder.buildObject(); - 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 = Configuration.getMarshallerFactory() - .getMarshaller(authnRequest); - Element authDOM = marshaller.marshall(authnRequest); - StringWriter requestWriter = new StringWriter(); - XMLHelper.writeNode(authDOM, requestWriter); - 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(); - String encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream.toByteArray(), Base64.DONT_BREAK_LINES); - encodedRequestMessage = URLEncoder.encode(encodedRequestMessage, "UTF-8").trim(); - return encodedRequestMessage; - } - public Response processSAMLResponse(String responseMessage) { - XMLObject responseObject = null; + Response responseObject = null; try { - responseObject = this.unmarshall(responseMessage); + responseObject = SAMLUtils.decodeSAMLResponse(responseMessage); } catch (ConfigurationException | ParserConfigurationException | SAXException | IOException | UnmarshallingException e) { s_logger.error("SAMLResponse processing error: " + e.getMessage()); } - return (Response) responseObject; - } - - private XMLObject unmarshall(String responseMessage) - throws ConfigurationException, ParserConfigurationException, - SAXException, IOException, UnmarshallingException { - try { - DefaultBootstrap.bootstrap(); - } catch (ConfigurationException | FactoryConfigurationError e) { - s_logger.error("SAML response message decoding error: " + e.getMessage()); - } - DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); - documentBuilderFactory.setNamespaceAware(true); - 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); + return responseObject; } @Override @@ -282,7 +170,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent if (nameId.getFormat().equals(NameIDType.PERSISTENT) || nameId.getFormat().equals(NameIDType.EMAIL)) { username = nameId.getValue(); - uniqueUserId = "saml-" + username; + uniqueUserId = SAMLUtils.createSAMLId(username); if (nameId.getFormat().equals(NameIDType.EMAIL)) { email = username; } @@ -299,7 +187,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent String attributeValue = attribute.getAttributeValues().get(0).getDOM().getTextContent(); if (attributeName.equalsIgnoreCase("uid") && uniqueUserId == null) { username = attributeValue; - uniqueUserId = "saml-" + username; + uniqueUserId = SAMLUtils.createSAMLId(username); } else if (attributeName.equalsIgnoreCase("givenName")) { firstName = attributeValue; } else if (attributeName.equalsIgnoreCase(("sn"))) { http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d45b3035/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java ---------------------------------------------------------------------- diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java index b82f2c8..32e2f99 100644 --- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java +++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java @@ -31,7 +31,7 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.util.Map; -@APICommand(name = "samlslo", description = "SAML Single Log Out API", responseObject = LogoutCmdResponse.class, entityType = {}) +@APICommand(name = "samlslo", description = "SAML Global Log Out API", responseObject = LogoutCmdResponse.class, entityType = {}) public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator { public static final Logger s_logger = Logger.getLogger(SAML2LogoutAPIAuthenticatorCmd.class.getName()); private static final String s_name = "logoutresponse"; http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d45b3035/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java ---------------------------------------------------------------------- diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java index 1a37f65..a4902d1 100644 --- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java +++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java @@ -21,6 +21,7 @@ import com.cloud.user.UserAccount; import com.cloud.user.dao.UserAccountDao; import com.cloud.user.dao.UserDao; import com.cloud.utils.Pair; +import org.apache.cloudstack.utils.auth.SAMLUtils; import org.apache.log4j.Logger; import javax.ejb.Local; @@ -48,7 +49,7 @@ public class SAML2UserAuthenticator extends DefaultUserAuthenticator { } else { User user = _userDao.getUser(userAccount.getId()); // TODO: check SAMLRequest, signature etc. from requestParameters - if (user != null && user.getUuid().startsWith("saml")) { + if (user != null && SAMLUtils.checkSAMLUserId(user.getUuid())) { return new Pair<Boolean, ActionOnFailedAuthentication>(true, null); } }