changeset 2943:7683d4e43afa

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
author Bjoern Ricks <bjoern.ricks@intevation.de>
date Tue, 10 Jul 2012 10:49:18 +0000
parents 5885c5b8d71d
children 0889ec33249c
files flys-client/src/main/java/de/intevation/flys/client/server/was/Assertion.java flys-client/src/main/java/de/intevation/flys/client/server/was/Namespaces.java flys-client/src/main/java/de/intevation/flys/client/server/was/Request.java flys-client/src/main/java/de/intevation/flys/client/server/was/Response.java flys-client/src/main/java/de/intevation/flys/client/server/was/ServiceException.java flys-client/src/main/java/de/intevation/flys/client/server/was/Signature.java
diffstat 6 files changed, 478 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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<String> 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<String>();
+
+        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<String> 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:
--- /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#");
+
+}
--- /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:
+
--- /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:
--- /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:
--- /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:

http://dive4elements.wald.intevation.org