# HG changeset patch # User Bjoern Ricks # Date 1341917358 0 # Node ID 7683d4e43afa99e05ca57a82f1cb379db514a2da # Parent 5885c5b8d71db02a422e5a532bd7a7f0a094acde Implement class representation of a Web Authentication Service (WAS) request and response. If the authentication is successful the WAS responses with a base64 encoded Security Assertion Markup Language. The current implementation of the saml response simplifies the protocol and misses validation. flys-client/trunk@4909 c6561f87-3c4e-4783-a992-168aeb5c3f6f diff -r 5885c5b8d71d -r 7683d4e43afa flys-client/src/main/java/de/intevation/flys/client/server/was/Assertion.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flys-client/src/main/java/de/intevation/flys/client/server/was/Assertion.java Tue Jul 10 10:49:18 2012 +0000 @@ -0,0 +1,178 @@ +package de.intevation.flys.client.server.was; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Iterator; +import java.util.Date; +import java.util.List; +import java.util.LinkedList; + +import org.apache.log4j.Logger; + +import org.jdom.Element; + +public class Assertion { + + private static Logger logger = Logger.getLogger(Assertion.class); + + private Element assertion; + private LinkedList roles; + private String assertion_id; + private String user_id; + private String name_id; + private String group_id; + private String group_name; + private Date notbefore; + private Date notonorafter; + private Signature signature; + + private static final String ATTR_CONT_USER_ID = + "urn:conterra:names:sdi-suite:policy:attribute:user-id"; + private static final String ATTR_CONT_GROUP_ID = + "urn:conterra:names:sdi-suite:policy:attribute:group-id"; + private static final String ATTR_CONT_GROUP_NAME = + "urn:conterra:names:sdi-suite:policy:attribute:group-name"; + private static final String ATTR_CONT_ROLE = + "urn:conterra:names:sdi-suite:policy:attribute:role"; + + + public Assertion(Element assertion) { + this.assertion = assertion; + this.roles = new LinkedList(); + + this.assertion_id = assertion.getAttributeValue("AssertionID"); + + this.parseContition(); + this.parseAttributeStatement(); + } + + private void parseContition() { + Element condition = this.assertion.getChild("Conditions", + Namespaces.SAML_NS_ASSERT); + if (condition != null) { + SimpleDateFormat dateformat = new SimpleDateFormat(); + // format should be "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" but that's only + // available in java 7+ + dateformat.applyPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + String from = condition.getAttributeValue("NotBefore"); + if (from != null) { + try { + this.notbefore = dateformat.parse(from); + } + catch(ParseException e) { + logger.error("Unknown datetime format for Condition " + "NotBefore " + from); + } + } + + String until = condition.getAttributeValue("NotOnOrAfter"); + if (until != null) { + try { + this.notonorafter = dateformat.parse(until); + } + catch(ParseException e) { + logger.error("Unknown datetime format for Condition " + "NotOnOrAfter " + until); + } + } + } + } + + private void parseAttributeStatement() { + Element attrstatement = this.assertion.getChild("AttributeStatement", + Namespaces.SAML_NS_ASSERT); + if (attrstatement != null) { + + Element subject = attrstatement.getChild("Subject", + Namespaces.SAML_NS_ASSERT); + if (subject != null) { + this.name_id = subject.getChildText("NameIdentifier", + Namespaces.SAML_NS_ASSERT); + } + + List attributes = attrstatement.getChildren("Attribute", + Namespaces.SAML_NS_ASSERT); + for(Iterator i = attributes.iterator(); i.hasNext();) { + Element attr = (Element)i.next(); + String attrname = attr.getAttributeValue("AttributeName"); + if (attrname.equals(ATTR_CONT_USER_ID)) { + this.user_id = this.getAttributeValue(attr); + } + else if (attrname.equals(ATTR_CONT_GROUP_ID)) { + this.group_id = this.getAttributeValue(attr); + } + else if (attrname.equals(ATTR_CONT_GROUP_NAME)) { + this.group_name = this.getAttributeValue(attr); + } + else if (attrname.equals(ATTR_CONT_ROLE)) { + List roles = attr.getChildren("AttributeValue", + Namespaces.SAML_NS_ASSERT); + for(Iterator j = roles.iterator(); j.hasNext();) { + Element role = (Element)j.next(); + this.roles.add(role.getText()); + } + } + else { + logger.debug("Unknown AttributeName " + attrname + + " found while parsing AttributeStatement."); + } + } + } + } + + private String getAttributeValue(Element attr) { + return attr.getChildText("AttributeValue", Namespaces.SAML_NS_ASSERT); + } + + public List getRoles() { + return this.roles; + } + + public Boolean isValid() { + // TODO: + // check signature digest + // check signature value + // check signature cert + return false; + } + + public Signature getSiganture() { + if (this.signature == null) { + Element signature = this.assertion.getChild("Signature", + Namespaces.XML_SIG_NS); + if (signature != null) { + this.signature = new Signature(signature); + } + } + return this.signature; + } + + public String getUserID() { + return this.user_id; + } + + public String getNameID() { + return this.name_id; + } + + public String getGroupID() { + return this.group_id; + } + + public String getGroupName() { + return this.group_name; + } + + public String getID() { + return this.assertion_id; + } + + public Date getFrom() { + return this.notbefore; + } + + public Date getUntil() { + return this.notonorafter; + } +} +// vim: set fileencoding=utf-8 ts=4 sw=4 et si tw=80: diff -r 5885c5b8d71d -r 7683d4e43afa flys-client/src/main/java/de/intevation/flys/client/server/was/Namespaces.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flys-client/src/main/java/de/intevation/flys/client/server/was/Namespaces.java Tue Jul 10 10:49:18 2012 +0000 @@ -0,0 +1,14 @@ +package de.intevation.flys.client.server.was; + +import org.jdom.Namespace; + +public class Namespaces { + + public static final Namespace SAML_NS_ASSERT = + Namespace.getNamespace("urn:oasis:names:tc:SAML:1.0:assertion"); + public static final Namespace SAML_NS_PROTO = + Namespace.getNamespace("urn:oasis:names:tc:SAML:1.0:protocol"); + public static final Namespace XML_SIG_NS = + Namespace.getNamespace("http://www.w3.org/2000/09/xmldsig#"); + +} diff -r 5885c5b8d71d -r 7683d4e43afa flys-client/src/main/java/de/intevation/flys/client/server/was/Request.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flys-client/src/main/java/de/intevation/flys/client/server/was/Request.java Tue Jul 10 10:49:18 2012 +0000 @@ -0,0 +1,61 @@ +package de.intevation.flys.client.server.was; + +import java.io.UnsupportedEncodingException; +import java.net.URI; + +import org.apache.commons.codec.binary.Base64; +import org.apache.http.client.methods.HttpGet; +import org.apache.log4j.Logger; + +public class Request extends HttpGet { + + private final static String VERSION = "1.1"; + private final static String REQUEST_SAML_RESPONSE = "GetSAMLResponse"; + private final static String METHOD_AUTH_PASSWORD = + "urn:opengeospatial:authNMethod:OWS:1.0:password"; + + private static Logger logger = Logger.getLogger(Request.class); + + public Request(String uri) { + String request = uri + "?VERSION=" + VERSION + "&REQUEST=" + + REQUEST_SAML_RESPONSE + "&METHOD=" + METHOD_AUTH_PASSWORD + + "&ANONYMOUS=TRUE&CREDENTIALS="; + this.setURI(URI.create(request)); + } + + public Request(String uri, String user, String pass, String encoding) { + try { + String base64user = this.toBase64(user, encoding); + String base64pass = this.toBase64(pass, encoding); + + String request = uri + "?VERSION=" + VERSION + "&REQUEST=" + + REQUEST_SAML_RESPONSE + "&METHOD=" + METHOD_AUTH_PASSWORD + + "&CREDENTIALS=" + base64user + "," + base64pass; + + System.out.println(request); + + this.setURI(URI.create(request)); + } + catch(UnsupportedEncodingException e) { + logger.error(e); + } + } + + private String toBase64(String value, String encoding) throws + UnsupportedEncodingException { + if (encoding == null) { + encoding = "utf-8"; + } + try { + return new String(Base64.encodeBase64(value.getBytes(encoding))); + } + catch(UnsupportedEncodingException e) { + logger.warn("Can't encode string with encoding " + encoding + + ". Falling back to utf-8. " + e); + return this.toBase64(value, "utf-8"); + } + } + +} +// vim: set et si fileencoding=utf-8 ts=4 sw=4 tw=80: + diff -r 5885c5b8d71d -r 7683d4e43afa flys-client/src/main/java/de/intevation/flys/client/server/was/Response.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flys-client/src/main/java/de/intevation/flys/client/server/was/Response.java Tue Jul 10 10:49:18 2012 +0000 @@ -0,0 +1,93 @@ +package de.intevation.flys.client.server.was; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpEntity; +import org.apache.log4j.Logger; + +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.input.SAXBuilder; + +public class Response { + + private static Logger logger = Logger.getLogger(Response.class); + + private Element root; + private Assertion assertion; + + public Response(HttpEntity entity) throws ServiceException { + + if (entity == null) { + throw new ServiceException("Invalid response"); + } + + String contenttype = entity.getContentType().getValue(); + + + try{ + InputStream in = entity.getContent(); + + if (!contenttype.equals("application/vnd.ogc.se_xml")) { + // assume base64 + byte[] content = IOUtils.toByteArray(entity.getContent()); + in = new ByteArrayInputStream(Base64.decodeBase64(content)); + } + + SAXBuilder builder = new SAXBuilder(); + Document doc = builder.build(in); + Element root = doc.getRootElement(); + + if (root.getName() == "ServiceExceptionReport") { + throw new ServiceException(root.getChildText("ServiceException")); + } + + this.root = root; + } + catch(JDOMException e) { + logger.error(e); + } + catch(IOException e) { + logger.error(e); + } + } + + public Element getRoot() { + return this.root; + } + + public Boolean isSuccess() { + return this.getStatus() == "samlp:Success"; + } + + public String getStatus() { + Element status = this.root.getChild("Status", Namespaces.SAML_NS_PROTO); + if (status == null) { + return null; + } + Element statuscode = status.getChild("StatusCode", + Namespaces.SAML_NS_PROTO); + if (statuscode == null) { + return null; + } + return statuscode.getAttributeValue("Value"); + } + + public Assertion getAssertion() { + if (this.assertion == null && this.root != null) { + Element assertion = this.root.getChild("Assertion", + Namespaces.SAML_NS_ASSERT); + if (assertion != null) { + this.assertion = new Assertion(assertion); + } + } + return this.assertion; + } +} +// vim: set si et fileencoding=utf-8 ts=4 sw=4 tw=80: diff -r 5885c5b8d71d -r 7683d4e43afa flys-client/src/main/java/de/intevation/flys/client/server/was/ServiceException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flys-client/src/main/java/de/intevation/flys/client/server/was/ServiceException.java Tue Jul 10 10:49:18 2012 +0000 @@ -0,0 +1,11 @@ +package de.intevation.flys.client.server.was; + +import java.lang.Exception; + +public class ServiceException extends Exception { + + public ServiceException(String message) { + super(message); + } +} +// vim: set si et fileencoding=utf-8 ts=4 sw=4 tw=80: diff -r 5885c5b8d71d -r 7683d4e43afa flys-client/src/main/java/de/intevation/flys/client/server/was/Signature.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flys-client/src/main/java/de/intevation/flys/client/server/was/Signature.java Tue Jul 10 10:49:18 2012 +0000 @@ -0,0 +1,121 @@ +package de.intevation.flys.client.server.was; + +import java.io.ByteArrayInputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; + +import org.apache.commons.codec.binary.Base64; +import org.apache.log4j.Logger; + +import org.jdom.Element; + +public class Signature { + + private static Logger logger = Logger.getLogger(Signature.class); + + private static final String XML_SIG_DIGEST_SHA1 = + "http://www.w3.org/2000/09/xmldsig#sha1"; + private static final String XML_SIG_SIGNATURE_RSA_SHA1 = + "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + + private Element signature; + private Certificate cert; + private byte[] value; + private byte[] digestvalue; + private String reference; + + public Signature(Element signature) { + this.signature = signature; + this.parseSignatureInfo(); + this.parseSignatureValue(); + this.parseCertificate(); + } + + private void parseSignatureInfo() { + Element signatureinfo = this.signature.getChild("SignedInfo", + Namespaces.XML_SIG_NS); + if (signatureinfo != null) { + Element signaturemethod = signatureinfo.getChild("SignatureMethod", + Namespaces.XML_SIG_NS); + String algorithm = signaturemethod.getAttributeValue("Algorithm"); + if (!algorithm.equals(XML_SIG_SIGNATURE_RSA_SHA1)) { + logger.warn("Unkown signature alorithm " + algorithm); + } + + // There could be several references in XML-Sig spec but for me it + // doesn't make sense to have more then one in a SAML Assertion + Element reference = signatureinfo.getChild("Reference", + Namespaces.XML_SIG_NS); + // reference must be present but its better to check + if (reference != null) { + String digestvalue = reference.getChildText("DigestValue", + Namespaces.XML_SIG_NS); + String digestmethod = reference.getChildText("DigestMethod", + Namespaces.XML_SIG_NS); + if (!digestmethod.equals(XML_SIG_DIGEST_SHA1)) { + logger.warn("Unknown digest method " + digestmethod); + } + this.digestvalue = Base64.decodeBase64(digestvalue); + + String referenceuri = reference.getAttributeValue("URI"); + if (referenceuri.startsWith("#")) { + this.reference = referenceuri.substring(1); + } + else { + logger.warn("Unkown reference type " + referenceuri); + this.reference = referenceuri; + } + } + } + } + + private void parseSignatureValue() { + String signaturevalue = this.signature.getChildText("SignatureValue", + Namespaces.XML_SIG_NS); + this.value = Base64.decodeBase64(signaturevalue); + } + + private void parseCertificate() { + Element keyinfo = this.signature.getChild("KeyInfo", + Namespaces.XML_SIG_NS); + if (keyinfo != null) { + Element data = keyinfo.getChild("X509Data", Namespaces.XML_SIG_NS); + if (data != null) { + String base64cert = data.getChildText("X509Certificate", + Namespaces.XML_SIG_NS); + if (base64cert != null) { + byte[] bytes = Base64.decodeBase64(base64cert); + try { + CertificateFactory cf = CertificateFactory.getInstance( + "X.509"); + this.cert = cf.generateCertificate( + new ByteArrayInputStream(bytes)); + } + catch(CertificateException e) { + // should never occur + logger.error(e); + } + } + } + } + } + + public Certificate getCertificate() { + return this.cert; + } + + public byte[] getValue() { + return this.value; + } + + public String getReference() { + // In theory there could be several references with digestvalues, ... + return this.reference; + } + + public byte[] getDigestValue() { + return this.digestvalue; + } +} +// vim: set si et fileencoding=utf-8 ts=4 sw=4 tw=80: