keycloak-uncached

Changes

saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/AbstractStaxSamlParser.java 50(+0 -50)

Details

diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java b/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java
index 4f8a9a8..aee8091 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java
@@ -32,6 +32,7 @@ import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
 import javax.xml.XMLConstants;
+import javax.xml.datatype.Duration;
 import javax.xml.namespace.QName;
 import javax.xml.stream.Location;
 import javax.xml.stream.XMLEventReader;
@@ -52,7 +53,10 @@ import javax.xml.validation.SchemaFactory;
 import javax.xml.validation.Validator;
 import java.io.InputStream;
 import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
+import java.util.StringTokenizer;
 import java.util.concurrent.atomic.AtomicBoolean;
 import javax.xml.datatype.XMLGregorianCalendar;
 
@@ -296,6 +300,20 @@ public class StaxParserUtil {
      *
      * @return
      */
+    public static Duration getXmlDurationAttributeValue(StartElement startElement, HasQName attrName) throws ParsingException {
+        Attribute attr = startElement.getAttributeByName(attrName.getQName());
+        String value = getAttributeValue(attr);
+        return value == null ? null : XMLTimeUtil.parseAsDuration(value);
+    }
+
+    /**
+     * Get the Attribute value
+     *
+     * @param startElement
+     * @param tag localpart of the qname of the attribute
+     *
+     * @return
+     */
     public static Integer getIntegerAttributeValue(StartElement startElement, HasQName attrName) {
         Attribute attr = startElement.getAttributeByName(attrName.getQName());
         String value = getAttributeValue(attr);
@@ -391,6 +409,29 @@ public class StaxParserUtil {
         return StaxParserUtil.getAttributeValueRP(attr);
     }
 
+    /**
+     * Parse a space delimited list of strings
+     *
+     * @param startElement
+     * @param attrName
+     *
+     * @return
+     */
+    public static List<String> getRequiredStringListAttributeValue(StartElement startElement, HasQName attrName) throws ParsingException{
+        List<String> protocolEnum = new ArrayList<>();
+
+        String val = StaxParserUtil.getRequiredAttributeValue(startElement, attrName);
+        if (StringUtil.isNotNull(val)) {
+            StringTokenizer st = new StringTokenizer(val);
+            while (st.hasMoreTokens()) {
+                protocolEnum.add(st.nextToken());
+            }
+
+        }
+
+        return protocolEnum;
+    }
+
     private static final String JDK_TRANSFORMER_PROPERTY = "picketlink.jdk.transformer";
 
     /**
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/AbstractStaxSamlMetadataParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/AbstractStaxSamlMetadataParser.java
new file mode 100644
index 0000000..9d9122a
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/AbstractStaxSamlMetadataParser.java
@@ -0,0 +1,24 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.saml.common.parsers.AbstractStaxParser;
+import org.keycloak.saml.processing.core.parsers.util.QNameEnumLookup;
+
+import javax.xml.namespace.QName;
+
+/**
+ * @author mhajas
+ */
+abstract public class AbstractStaxSamlMetadataParser<T> extends AbstractStaxParser<T, SAMLMetadataQNames> {
+
+    protected static final QNameEnumLookup<SAMLMetadataQNames> LOOKUP = new QNameEnumLookup(SAMLMetadataQNames.values());
+
+
+    public AbstractStaxSamlMetadataParser(SAMLMetadataQNames expectedStartElement) {
+        super(expectedStartElement.getQName(), SAMLMetadataQNames.UNKNOWN_ELEMENT);
+    }
+
+    @Override
+    protected SAMLMetadataQNames getElementFromName(QName name) {
+        return LOOKUP.from(name);
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLArtifactResolutionServiceParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLArtifactResolutionServiceParser.java
new file mode 100644
index 0000000..45ba488
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLArtifactResolutionServiceParser.java
@@ -0,0 +1,17 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+/**
+ * @author mhajas
+ */
+public class SAMLArtifactResolutionServiceParser extends SAMLIndexedEndpointTypeParser {
+
+    private static final SAMLArtifactResolutionServiceParser INSTANCE = new SAMLArtifactResolutionServiceParser();
+
+    public SAMLArtifactResolutionServiceParser() {
+        super(SAMLMetadataQNames.ARTIFACT_RESOLUTION_SERVICE);
+    }
+
+    public static SAMLArtifactResolutionServiceParser getInstance() {
+        return INSTANCE;
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAssertinIDRequestServiceParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAssertinIDRequestServiceParser.java
new file mode 100644
index 0000000..a7d7bee
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAssertinIDRequestServiceParser.java
@@ -0,0 +1,17 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+/**
+ * @author mhajas
+ */
+public class SAMLAssertinIDRequestServiceParser extends SAMLEndpointTypeParser {
+
+    private static final SAMLAssertinIDRequestServiceParser INSTANCE = new SAMLAssertinIDRequestServiceParser();
+
+    public SAMLAssertinIDRequestServiceParser() {
+        super(SAMLMetadataQNames.ASSERTION_ID_REQUEST_SERVICE);
+    }
+
+    public static SAMLAssertinIDRequestServiceParser getInstance() {
+        return INSTANCE;
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAssertionConsumerServiceParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAssertionConsumerServiceParser.java
new file mode 100644
index 0000000..28b54e4
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAssertionConsumerServiceParser.java
@@ -0,0 +1,17 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+/**
+ * @author mhajas
+ */
+public class SAMLAssertionConsumerServiceParser extends SAMLIndexedEndpointTypeParser {
+
+    private static final SAMLAssertionConsumerServiceParser INSTANCE = new SAMLAssertionConsumerServiceParser();
+
+    public SAMLAssertionConsumerServiceParser() {
+        super(SAMLMetadataQNames.ASSERTION_CONSUMER_SERVICE);
+    }
+
+    public static SAMLAssertionConsumerServiceParser getInstance() {
+        return INSTANCE;
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAttributeAuthorityDescriptorParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAttributeAuthorityDescriptorParser.java
new file mode 100644
index 0000000..0a1a83e
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAttributeAuthorityDescriptorParser.java
@@ -0,0 +1,65 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.metadata.AttributeAuthorityDescriptorType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+import java.util.List;
+
+/**
+ * @author mhajas
+ */
+public class SAMLAttributeAuthorityDescriptorParser extends SAMLRoleDecriptorTypeParser<AttributeAuthorityDescriptorType> {
+
+    private static final SAMLAttributeAuthorityDescriptorParser INSTANCE = new SAMLAttributeAuthorityDescriptorParser();
+
+    public SAMLAttributeAuthorityDescriptorParser() {
+        super(SAMLMetadataQNames.ATTRIBUTE_AUTHORITY_DESCRIPTOR);
+    }
+
+    public static SAMLAttributeAuthorityDescriptorParser getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    protected AttributeAuthorityDescriptorType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        List<String> protocolEnum = StaxParserUtil.getRequiredStringListAttributeValue(element, SAMLMetadataQNames.ATTR_PROTOCOL_SUPPORT_ENUMERATION);
+        AttributeAuthorityDescriptorType descriptor = new AttributeAuthorityDescriptorType(protocolEnum);
+
+        parseOptionalArguments(element, descriptor);
+
+        return descriptor;
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, AttributeAuthorityDescriptorType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch (element) {
+            case ATTRIBUTE_SERVICE:
+            target.addAttributeService(SAMLAttributeServiceParser.getInstance().parse(xmlEventReader));
+            break;
+
+        case ASSERTION_ID_REQUEST_SERVICE:
+            target.addAssertionIDRequestService(SAMLAssertinIDRequestServiceParser.getInstance().parse(xmlEventReader));
+            break;
+
+        case NAMEID_FORMAT:
+            StaxParserUtil.advance(xmlEventReader);
+            target.addNameIDFormat(StaxParserUtil.getElementText(xmlEventReader));
+            break;
+
+        case ATTRIBUTE_PROFILE:
+            StaxParserUtil.advance(xmlEventReader);
+            target.addAttributeProfile(StaxParserUtil.getElementText(xmlEventReader));
+            break;
+
+        case ATTRIBUTE:
+            target.addAttribute(SAMLAttributeParser.getInstance().parse(xmlEventReader));
+            break;
+
+        default:
+            super.processSubElement(xmlEventReader, target, element, elementDetail);
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAttributeConsumingServiceParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAttributeConsumingServiceParser.java
new file mode 100644
index 0000000..6bdded9
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAttributeConsumingServiceParser.java
@@ -0,0 +1,61 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.metadata.AttributeConsumingServiceType;
+import org.keycloak.dom.saml.v2.metadata.LocalizedNameType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+
+import static org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLMetadataQNames.ATTR_LANG;
+
+
+/**
+ * @author mhajas
+ */
+public class SAMLAttributeConsumingServiceParser extends AbstractStaxSamlMetadataParser<AttributeConsumingServiceType> {
+
+    private static final SAMLAttributeConsumingServiceParser INSTANCE = new SAMLAttributeConsumingServiceParser();
+
+    public SAMLAttributeConsumingServiceParser() {
+        super(SAMLMetadataQNames.ATTRIBUTE_CONSUMING_SERVICE);
+    }
+
+    public static SAMLAttributeConsumingServiceParser getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    protected AttributeConsumingServiceType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        int index = Integer.parseInt(StaxParserUtil.getRequiredAttributeValue(element, SAMLMetadataQNames.ATTR_INDEX));
+
+        AttributeConsumingServiceType service = new AttributeConsumingServiceType(index);
+        service.setIsDefault(StaxParserUtil.getBooleanAttributeValue(element, SAMLMetadataQNames.ATTR_IS_DEFAULT));
+
+        return service;
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, AttributeConsumingServiceType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch (element) {
+            case SERVICE_NAME:
+                LocalizedNameType serviceName = new LocalizedNameType(StaxParserUtil.getAttributeValue(elementDetail, ATTR_LANG));
+                StaxParserUtil.advance(xmlEventReader);
+                serviceName.setValue(StaxParserUtil.getElementText(xmlEventReader));
+                target.addServiceName(serviceName);
+                break;
+
+            case SERVICE_DESCRIPTION:
+                target.addServiceDescription(new LocalizedNameType(StaxParserUtil.getAttributeValue(elementDetail, ATTR_LANG)));
+                break;
+
+            case REQUESTED_ATTRIBUTE:
+                target.addRequestedAttribute(SAMLRequestedAttributeParser.getInstance().parse(xmlEventReader));
+                break;
+
+            default:
+                throw LOGGER.parserUnknownTag(StaxParserUtil.getElementName(elementDetail), elementDetail.getLocation());
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAttributeParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAttributeParser.java
new file mode 100644
index 0000000..0b719a2
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAttributeParser.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.assertion.AttributeType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+import org.keycloak.saml.processing.core.parsers.saml.assertion.SAMLAttributeValueParser;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+
+/**
+ * Parse the <conditions> in the saml assertion
+ *
+ * @since Oct 14, 2010
+ */
+public class SAMLAttributeParser extends AbstractStaxSamlMetadataParser<AttributeType> {
+
+    private static final SAMLAttributeParser INSTANCE = new SAMLAttributeParser();
+
+    private SAMLAttributeParser() {
+        super(SAMLMetadataQNames.ATTRIBUTE);
+    }
+
+    public static SAMLAttributeParser getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    protected AttributeType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        String name = StaxParserUtil.getRequiredAttributeValue(element, SAMLMetadataQNames.ATTR_NAME);
+        final AttributeType attribute = new AttributeType(name);
+
+        attribute.setFriendlyName(StaxParserUtil.getAttributeValue(element, SAMLMetadataQNames.ATTR_FRIENDLY_NAME));
+        attribute.setNameFormat(StaxParserUtil.getAttributeValue(element, SAMLMetadataQNames.ATTR_NAME_FORMAT));
+
+        final String x500Encoding = StaxParserUtil.getAttributeValue(element, SAMLMetadataQNames.ATTR_X500_ENCODING);
+        if (x500Encoding != null) {
+            attribute.getOtherAttributes().put(SAMLMetadataQNames.ATTR_X500_ENCODING.getQName(), x500Encoding);
+        }
+
+        return attribute;
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, AttributeType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch (element) {
+            case ATTRIBUTE_VALUE:
+                target.addAttributeValue(SAMLAttributeValueParser.getInstance().parse(xmlEventReader));
+                break;
+
+            default:
+                throw LOGGER.parserUnknownTag(StaxParserUtil.getElementName(elementDetail), elementDetail.getLocation());
+        }
+    }
+}
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAttributeServiceParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAttributeServiceParser.java
new file mode 100644
index 0000000..a8d15cd
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAttributeServiceParser.java
@@ -0,0 +1,17 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+/**
+ * @author mhajas
+ */
+public class SAMLAttributeServiceParser extends SAMLEndpointTypeParser {
+
+    private static final SAMLAttributeServiceParser INSTANCE = new SAMLAttributeServiceParser();
+
+    public SAMLAttributeServiceParser() {
+        super(SAMLMetadataQNames.ATTRIBUTE_SERVICE);
+    }
+
+    public static SAMLAttributeServiceParser getInstance() {
+        return INSTANCE;
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAuthnAuthorityDescriptorParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAuthnAuthorityDescriptorParser.java
new file mode 100644
index 0000000..f676206
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAuthnAuthorityDescriptorParser.java
@@ -0,0 +1,56 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.metadata.AuthnAuthorityDescriptorType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+import java.util.List;
+
+/**
+ * @author mhajas
+ */
+public class SAMLAuthnAuthorityDescriptorParser extends SAMLRoleDecriptorTypeParser<AuthnAuthorityDescriptorType> {
+
+    private static final SAMLAuthnAuthorityDescriptorParser INSTANCE = new SAMLAuthnAuthorityDescriptorParser();
+
+    public SAMLAuthnAuthorityDescriptorParser() {
+        super(SAMLMetadataQNames.AUTHN_AUTHORITY_DESCRIPTOR);
+    }
+
+    public static SAMLAuthnAuthorityDescriptorParser getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    protected AuthnAuthorityDescriptorType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        List<String> protocolEnum = StaxParserUtil.getRequiredStringListAttributeValue(element, SAMLMetadataQNames.ATTR_PROTOCOL_SUPPORT_ENUMERATION);
+        AuthnAuthorityDescriptorType descriptor = new AuthnAuthorityDescriptorType(protocolEnum);
+
+        parseOptionalArguments(element, descriptor);
+
+        return descriptor;
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, AuthnAuthorityDescriptorType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch (element) {
+        case AUTHN_QUERY_SERVICE:
+            target.addAuthnQueryService(SAMLAuthnQueryServiceParser.getInstance().parse(xmlEventReader));
+            break;
+
+        case ASSERTION_ID_REQUEST_SERVICE:
+            target.addAssertionIDRequestService(SAMLAssertinIDRequestServiceParser.getInstance().parse(xmlEventReader));
+            break;
+
+        case NAMEID_FORMAT:
+            StaxParserUtil.advance(xmlEventReader);
+            target.addNameIDFormat(StaxParserUtil.getElementText(xmlEventReader));
+            break;
+
+        default:
+            super.processSubElement(xmlEventReader, target, element, elementDetail);
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAuthnQueryServiceParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAuthnQueryServiceParser.java
new file mode 100644
index 0000000..9f10ce4
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAuthnQueryServiceParser.java
@@ -0,0 +1,17 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+/**
+ * @author mhajas
+ */
+public class SAMLAuthnQueryServiceParser extends SAMLEndpointTypeParser {
+
+    private static final SAMLAuthnQueryServiceParser INSTANCE = new SAMLAuthnQueryServiceParser();
+
+    public SAMLAuthnQueryServiceParser() {
+        super(SAMLMetadataQNames.AUTHN_QUERY_SERVICE);
+    }
+
+    public static SAMLAuthnQueryServiceParser getInstance() {
+        return INSTANCE;
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAuthzServiceParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAuthzServiceParser.java
new file mode 100644
index 0000000..1c489a3
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAuthzServiceParser.java
@@ -0,0 +1,17 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+/**
+ * @author mhajas
+ */
+public class SAMLAuthzServiceParser extends SAMLEndpointTypeParser {
+
+    private static final SAMLAuthzServiceParser INSTANCE = new SAMLAuthzServiceParser();
+
+    public SAMLAuthzServiceParser() {
+        super(SAMLMetadataQNames.AUTHZ_SERVICE);
+    }
+
+    public static SAMLAuthzServiceParser getInstance() {
+        return INSTANCE;
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLContactPersonParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLContactPersonParser.java
new file mode 100644
index 0000000..aa16eb3
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLContactPersonParser.java
@@ -0,0 +1,70 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.metadata.ContactType;
+import org.keycloak.dom.saml.v2.metadata.ContactTypeType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+
+import static org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLMetadataQNames.ATTR_CONTACT_TYPE;
+import static org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLMetadataQNames.CONTACT_PERSON;
+
+/**
+ * @author mhajas
+ */
+public class SAMLContactPersonParser extends AbstractStaxSamlMetadataParser<ContactType> {
+
+    private static final SAMLContactPersonParser INSTANCE = new SAMLContactPersonParser();
+
+    public SAMLContactPersonParser() {
+        super(CONTACT_PERSON);
+    }
+
+    public static SAMLContactPersonParser getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    protected ContactType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        return new ContactType(ContactTypeType.fromValue(StaxParserUtil.getRequiredAttributeValue(element, ATTR_CONTACT_TYPE)));
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, ContactType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch (element) {
+            case COMPANY:
+                StaxParserUtil.advance(xmlEventReader);
+                target.setCompany(StaxParserUtil.getElementText(xmlEventReader));
+                break;
+
+            case GIVEN_NAME:
+                StaxParserUtil.advance(xmlEventReader);
+                target.setGivenName(StaxParserUtil.getElementText(xmlEventReader));
+                break;
+
+            case SURNAME:
+                StaxParserUtil.advance(xmlEventReader);
+                target.setSurName(StaxParserUtil.getElementText(xmlEventReader));
+                break;
+
+            case EMAIL_ADDRESS:
+                StaxParserUtil.advance(xmlEventReader);
+                target.addEmailAddress(StaxParserUtil.getElementText(xmlEventReader));
+                break;
+
+            case TELEPHONE_NUMBER:
+                StaxParserUtil.advance(xmlEventReader);
+                target.addTelephone(StaxParserUtil.getElementText(xmlEventReader));
+                break;
+
+            case EXTENSIONS:
+                target.setExtensions(SAMLExtensionsParser.getInstance().parse(xmlEventReader));
+                break;
+
+            default:
+                throw LOGGER.parserUnknownTag(StaxParserUtil.getElementName(elementDetail), elementDetail.getLocation());
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEncryptionMethodParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEncryptionMethodParser.java
new file mode 100644
index 0000000..70dec2a
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEncryptionMethodParser.java
@@ -0,0 +1,70 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.xmlsec.w3.xmlenc.EncryptionMethodType;
+import org.keycloak.saml.common.constants.GeneralConstants;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+import java.math.BigInteger;
+
+import static org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLMetadataQNames.ENCRYPTION_METHOD;
+
+/**
+ * @author mhajas
+ */
+public class SAMLEncryptionMethodParser extends AbstractStaxSamlMetadataParser<EncryptionMethodType> {
+
+    private static final SAMLEncryptionMethodParser INSTANCE = new SAMLEncryptionMethodParser();
+
+    public SAMLEncryptionMethodParser() {
+        super(ENCRYPTION_METHOD);
+    }
+
+    public static SAMLEncryptionMethodParser getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    protected EncryptionMethodType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        return new EncryptionMethodType(StaxParserUtil.getRequiredAttributeValue(element, SAMLMetadataQNames.ATTR_ALGORITHM));
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, EncryptionMethodType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch(element) {
+            case KEY_SIZE:
+                {
+                    StaxParserUtil.advance(xmlEventReader);
+                    BigInteger keySize = BigInteger.valueOf(Long.valueOf(StaxParserUtil.getElementText(xmlEventReader)));
+
+                    EncryptionMethodType.EncryptionMethod encMethod = target.getEncryptionMethod();
+                    if (encMethod == null) {
+                        encMethod = new EncryptionMethodType.EncryptionMethod();
+                        target.setEncryptionMethod(encMethod);
+                    }
+
+                    encMethod.setKeySize(keySize);
+                }
+                break;
+
+            case OAEP_PARAMS:
+                {
+                    StaxParserUtil.advance(xmlEventReader);
+                    byte[] OAEPparams = StaxParserUtil.getElementText(xmlEventReader).getBytes(GeneralConstants.SAML_CHARSET);
+                    EncryptionMethodType.EncryptionMethod encMethod = target.getEncryptionMethod();
+                    if (encMethod == null){
+                        encMethod = new EncryptionMethodType.EncryptionMethod();
+                        target.setEncryptionMethod(encMethod);
+                    }
+
+                    encMethod.setOAEPparams(OAEPparams);
+                }
+                break;
+
+            default:
+                throw LOGGER.parserUnknownTag(StaxParserUtil.getElementName(elementDetail), elementDetail.getLocation());
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEndpointTypeParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEndpointTypeParser.java
new file mode 100644
index 0000000..d0b276e
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEndpointTypeParser.java
@@ -0,0 +1,40 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.metadata.EndpointType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+import java.net.URI;
+
+/**
+ * @author mhajas
+ */
+public abstract class SAMLEndpointTypeParser extends AbstractStaxSamlMetadataParser<EndpointType> {
+
+    public SAMLEndpointTypeParser(SAMLMetadataQNames expectedStartElement) {
+        super(expectedStartElement);
+    }
+
+    @Override
+    protected EndpointType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        String binding = StaxParserUtil.getRequiredAttributeValue(element, SAMLMetadataQNames.ATTR_BINDING);
+        String location = StaxParserUtil.getRequiredAttributeValue(element, SAMLMetadataQNames.ATTR_LOCATION);
+
+        EndpointType endpoint = new EndpointType(URI.create(binding), URI.create(location));
+
+        String responseLocation = StaxParserUtil.getAttributeValue(element, SAMLMetadataQNames.ATTR_RESPONSE_LOCATION);
+
+        if (responseLocation != null) {
+            endpoint.setResponseLocation(URI.create(responseLocation));
+        }
+
+        return endpoint;
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, EndpointType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        throw LOGGER.parserUnknownTag(StaxParserUtil.getElementName(elementDetail), elementDetail.getLocation());
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEntitiesDescriptorParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEntitiesDescriptorParser.java
index 83e9179..56a1c34 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEntitiesDescriptorParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEntitiesDescriptorParser.java
@@ -17,24 +17,12 @@
 package org.keycloak.saml.processing.core.parsers.saml.metadata;
 
 import org.keycloak.dom.saml.v2.metadata.EntitiesDescriptorType;
-import org.keycloak.dom.saml.v2.metadata.ExtensionsType;
-import org.keycloak.saml.common.PicketLinkLogger;
-import org.keycloak.saml.common.PicketLinkLoggerFactory;
-import org.keycloak.saml.common.constants.JBossSAMLConstants;
-import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.common.exceptions.ParsingException;
-import org.keycloak.saml.common.parsers.AbstractParser;
 import org.keycloak.saml.common.util.StaxParserUtil;
-import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Element;
 
-import javax.xml.namespace.QName;
 import javax.xml.stream.XMLEventReader;
-import javax.xml.stream.events.Attribute;
-import javax.xml.stream.events.EndElement;
 import javax.xml.stream.events.StartElement;
-import javax.xml.stream.events.XMLEvent;
-import org.keycloak.saml.common.parsers.StaxParser;
 
 /**
  * Parse the SAML Entities Descriptor
@@ -42,74 +30,53 @@ import org.keycloak.saml.common.parsers.StaxParser;
  * @author Anil.Saldhana@redhat.com
  * @since Jan 31, 2011
  */
-public class SAMLEntitiesDescriptorParser extends AbstractParser {
+public class SAMLEntitiesDescriptorParser extends AbstractStaxSamlMetadataParser<EntitiesDescriptorType> {
 
-    private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
+    private static final SAMLEntitiesDescriptorParser INSTANCE = new SAMLEntitiesDescriptorParser();
 
-    private static final String EDT = JBossSAMLConstants.ENTITIES_DESCRIPTOR.get();
-
-    public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
+    public SAMLEntitiesDescriptorParser() {
+        super(SAMLMetadataQNames.ENTITIES_DESCRIPTOR);
+    }
 
-        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-        StaxParserUtil.validate(startElement, EDT);
+    public static SAMLEntitiesDescriptorParser getInstance() {
+        return INSTANCE;
+    }
 
-        EntitiesDescriptorType entitiesDescriptorType = new EntitiesDescriptorType();
+    @Override
+    protected EntitiesDescriptorType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        EntitiesDescriptorType descriptor = new EntitiesDescriptorType();
 
         // Parse the attributes
-        Attribute validUntil = startElement.getAttributeByName(new QName(JBossSAMLConstants.VALID_UNTIL.get()));
-        if (validUntil != null) {
-            String validUntilValue = StaxParserUtil.getAttributeValue(validUntil);
-            entitiesDescriptorType.setValidUntil(XMLTimeUtil.parse(validUntilValue));
-        }
+        descriptor.setID(StaxParserUtil.getAttributeValue(element, SAMLMetadataQNames.ATTR_ID));
+        descriptor.setValidUntil(StaxParserUtil.getXmlTimeAttributeValue(element, SAMLMetadataQNames.ATTR_VALID_UNTIL));
+        descriptor.setCacheDuration(StaxParserUtil.getXmlDurationAttributeValue(element, SAMLMetadataQNames.ATTR_CACHE_DURATION));
+        descriptor.setName(StaxParserUtil.getAttributeValue(element, SAMLMetadataQNames.ATTR_NAME));
 
-        Attribute id = startElement.getAttributeByName(new QName(JBossSAMLConstants.ID.get()));
-        if (id != null) {
-            entitiesDescriptorType.setID(StaxParserUtil.getAttributeValue(id));
-        }
-
-        Attribute name = startElement.getAttributeByName(new QName(JBossSAMLConstants.NAME.get()));
-        if (name != null) {
-            entitiesDescriptorType.setName(StaxParserUtil.getAttributeValue(name));
-        }
+        return descriptor;
+    }
 
-        Attribute cacheDuration = startElement.getAttributeByName(new QName(JBossSAMLConstants.CACHE_DURATION.get()));
-        if (cacheDuration != null) {
-            entitiesDescriptorType
-                    .setCacheDuration(XMLTimeUtil.parseAsDuration(StaxParserUtil.getAttributeValue(cacheDuration)));
-        }
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, EntitiesDescriptorType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch (element) {
+            case SIGNATURE:
+                Element sig = StaxParserUtil.getDOMElement(xmlEventReader);
+                target.setSignature(sig);
+                break;
 
-        // Get the Child Elements
-        while (xmlEventReader.hasNext()) {
-            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
-            if (xmlEvent instanceof EndElement) {
-                StaxParserUtil.validate((EndElement) xmlEvent, EDT);
-                StaxParserUtil.getNextEndElement(xmlEventReader);
+            case EXTENSIONS:
+                target.setExtensions(SAMLExtensionsParser.getInstance().parse(xmlEventReader));
                 break;
-            }
-            startElement = (StartElement) xmlEvent;
-            String localPart = startElement.getName().getLocalPart();
 
-            if (JBossSAMLConstants.ENTITY_DESCRIPTOR.get().equals(localPart)) {
-                SAMLEntityDescriptorParser entityParser = new SAMLEntityDescriptorParser();
-                entitiesDescriptorType.addEntityDescriptor(entityParser.parse(xmlEventReader));
-            } else if (JBossSAMLConstants.EXTENSIONS__METADATA.get().equalsIgnoreCase(localPart)) {
-                entitiesDescriptorType.setExtensions(parseExtensions(xmlEventReader));
-            } else if (JBossSAMLConstants.ENTITIES_DESCRIPTOR.get().equalsIgnoreCase(localPart)) {
-                SAMLEntitiesDescriptorParser parser = new SAMLEntitiesDescriptorParser();
-                entitiesDescriptorType.addEntityDescriptor(parser.parse(xmlEventReader));
-            } else if (localPart.equals(JBossSAMLConstants.SIGNATURE.get())) {
-                entitiesDescriptorType.setSignature(StaxParserUtil.getDOMElement(xmlEventReader));
-            } else
-                throw logger.parserUnknownTag(localPart, startElement.getLocation());
-        }
-        return entitiesDescriptorType;
-    }
+            case ENTITY_DESCRIPTOR:
+                target.addEntityDescriptor(SAMLEntityDescriptorParser.getInstance().parse(xmlEventReader));
+                break;
 
+            case ENTITIES_DESCRIPTOR:
+                target.addEntityDescriptor(parse(xmlEventReader));
+                break;
 
-    private ExtensionsType parseExtensions(XMLEventReader xmlEventReader) throws ParsingException {
-        ExtensionsType extensions = new ExtensionsType();
-        Element extElement = StaxParserUtil.getDOMElement(xmlEventReader);
-        extensions.setElement(extElement);
-        return extensions;
+            default:
+                throw LOGGER.parserUnknownTag(StaxParserUtil.getElementName(elementDetail), elementDetail.getLocation());
+        }
     }
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEntityDescriptorParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEntityDescriptorParser.java
index f94027d..9c4eb18 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEntityDescriptorParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEntityDescriptorParser.java
@@ -16,50 +16,20 @@
  */
 package org.keycloak.saml.processing.core.parsers.saml.metadata;
 
-import org.keycloak.dom.saml.v2.assertion.AttributeType;
 import org.keycloak.dom.saml.v2.metadata.AttributeAuthorityDescriptorType;
-import org.keycloak.dom.saml.v2.metadata.AttributeConsumingServiceType;
-import org.keycloak.dom.saml.v2.metadata.ContactType;
-import org.keycloak.dom.saml.v2.metadata.ContactTypeType;
-import org.keycloak.dom.saml.v2.metadata.EndpointType;
+import org.keycloak.dom.saml.v2.metadata.AuthnAuthorityDescriptorType;
 import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
-import org.keycloak.dom.saml.v2.metadata.ExtensionsType;
 import org.keycloak.dom.saml.v2.metadata.IDPSSODescriptorType;
-import org.keycloak.dom.saml.v2.metadata.IndexedEndpointType;
-import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
-import org.keycloak.dom.saml.v2.metadata.KeyTypes;
-import org.keycloak.dom.saml.v2.metadata.LocalizedNameType;
-import org.keycloak.dom.saml.v2.metadata.LocalizedURIType;
-import org.keycloak.dom.saml.v2.metadata.OrganizationType;
+import org.keycloak.dom.saml.v2.metadata.PDPDescriptorType;
 import org.keycloak.dom.saml.v2.metadata.RoleDescriptorType;
 import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
-import org.keycloak.dom.xmlsec.w3.xmlenc.EncryptionMethodType;
-import org.keycloak.saml.common.PicketLinkLogger;
-import org.keycloak.saml.common.PicketLinkLoggerFactory;
-import org.keycloak.saml.common.constants.GeneralConstants;
-import org.keycloak.saml.common.constants.JBossSAMLConstants;
-import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.common.exceptions.ParsingException;
-import org.keycloak.saml.common.parsers.AbstractParser;
 import org.keycloak.saml.common.util.StaxParserUtil;
-import org.keycloak.saml.common.util.StringUtil;
-import org.keycloak.saml.processing.core.parsers.saml.assertion.SAMLAttributeParser;
-import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 
-import org.w3c.dom.Element;
-
-import javax.xml.namespace.QName;
 import javax.xml.stream.XMLEventReader;
-import javax.xml.stream.events.Attribute;
-import javax.xml.stream.events.EndElement;
 import javax.xml.stream.events.StartElement;
-import javax.xml.stream.events.XMLEvent;
-import java.math.BigInteger;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.StringTokenizer;
-import org.keycloak.saml.common.parsers.StaxParser;
+
+import static org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLMetadataQNames.ENTITY_DESCRIPTOR;
 
 /**
  * Parse the SAML Metadata element "EntityDescriptor"
@@ -67,621 +37,107 @@ import org.keycloak.saml.common.parsers.StaxParser;
  * @author Anil.Saldhana@redhat.com
  * @since Dec 14, 2010
  */
-public class SAMLEntityDescriptorParser extends AbstractParser {
-
-    private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
-
-    private static final String EDT = JBossSAMLConstants.ENTITY_DESCRIPTOR.get();
-
-    public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
-
-        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-        StaxParserUtil.validate(startElement, EDT);
-
-        Attribute entityID = startElement.getAttributeByName(new QName(JBossSAMLConstants.ENTITY_ID.get()));
-        String entityIDValue = StaxParserUtil.getAttributeValue(entityID);
-        EntityDescriptorType entityDescriptorType = new EntityDescriptorType(entityIDValue);
-
-        Attribute validUntil = startElement.getAttributeByName(new QName(JBossSAMLConstants.VALID_UNTIL.get()));
-        if (validUntil != null) {
-            String validUntilValue = StaxParserUtil.getAttributeValue(validUntil);
-            entityDescriptorType.setValidUntil(XMLTimeUtil.parse(validUntilValue));
-        }
-
-        Attribute id = startElement.getAttributeByName(new QName(JBossSAMLConstants.ID.get()));
-        if (id != null) {
-            entityDescriptorType.setID(StaxParserUtil.getAttributeValue(id));
-        }
-
-        Attribute cacheDuration = startElement.getAttributeByName(new QName(JBossSAMLConstants.CACHE_DURATION.get()));
-        if (cacheDuration != null) {
-            entityDescriptorType.setCacheDuration(XMLTimeUtil.parseAsDuration(StaxParserUtil.getAttributeValue(cacheDuration)));
-        }
-
-        // Get the Child Elements
-        while (xmlEventReader.hasNext()) {
-            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
-            if (xmlEvent instanceof EndElement) {
-                StaxParserUtil.validate((EndElement) xmlEvent, EDT);
-                StaxParserUtil.getNextEndElement(xmlEventReader);
-                break;
-            }
-            startElement = (StartElement) xmlEvent;
-            String localPart = startElement.getName().getLocalPart();
-
-            if (JBossSAMLConstants.IDP_SSO_DESCRIPTOR.get().equals(localPart)) {
-                IDPSSODescriptorType idpSSO = parseIDPSSODescriptor(xmlEventReader);
+public class SAMLEntityDescriptorParser extends AbstractStaxSamlMetadataParser<EntityDescriptorType> {
 
-                EntityDescriptorType.EDTDescriptorChoiceType edtDescChoice = new EntityDescriptorType.EDTDescriptorChoiceType(idpSSO);
-                EntityDescriptorType.EDTChoiceType edtChoice = EntityDescriptorType.EDTChoiceType.oneValue(edtDescChoice);
-                entityDescriptorType.addChoiceType(edtChoice);
-            } else if (JBossSAMLConstants.SP_SSO_DESCRIPTOR.get().equals(localPart)) {
-                SPSSODescriptorType spSSO = parseSPSSODescriptor(xmlEventReader);
+    private static final SAMLEntityDescriptorParser INSTANCE = new SAMLEntityDescriptorParser();
 
-                EntityDescriptorType.EDTDescriptorChoiceType edtDescChoice = new EntityDescriptorType.EDTDescriptorChoiceType(spSSO);
-                EntityDescriptorType.EDTChoiceType edtChoice = EntityDescriptorType.EDTChoiceType.oneValue(edtDescChoice);
-                entityDescriptorType.addChoiceType(edtChoice);
-            } else if (JBossSAMLConstants.ATTRIBUTE_AUTHORITY_DESCRIPTOR.get().equals(localPart)) {
-                AttributeAuthorityDescriptorType attrAuthority = parseAttributeAuthorityDescriptor(xmlEventReader);
-
-                EntityDescriptorType.EDTDescriptorChoiceType edtDescChoice = new EntityDescriptorType.EDTDescriptorChoiceType(attrAuthority);
-                EntityDescriptorType.EDTChoiceType edtChoice = EntityDescriptorType.EDTChoiceType.oneValue(edtDescChoice);
-                entityDescriptorType.addChoiceType(edtChoice);
-            } else if (JBossSAMLConstants.AUTHN_AUTHORITY_DESCRIPTOR.get().equals(localPart)) {
-                throw logger.unsupportedType("AuthnAuthorityDescriptor");
-            } else if (JBossSAMLConstants.AFFILIATION_DESCRIPTOR.get().equals(localPart)) {
-                throw logger.unsupportedType(" AffiliationDescriptor");
-            } else if (JBossSAMLConstants.PDP_DESCRIPTOR.get().equals(localPart)) {
-                throw logger.unsupportedType(" PDPDescriptor");
-            } else if (localPart.equals(JBossSAMLConstants.SIGNATURE.get())) {
-                entityDescriptorType.setSignature(StaxParserUtil.getDOMElement(xmlEventReader));
-            } else if (JBossSAMLConstants.ORGANIZATION.get().equals(localPart)) {
-                OrganizationType organization = parseOrganization(xmlEventReader);
-
-                entityDescriptorType.setOrganization(organization);
-            } else if (JBossSAMLConstants.CONTACT_PERSON.get().equals(localPart)) {
-                entityDescriptorType.addContactPerson(parseContactPerson(xmlEventReader));
-            } else if (JBossSAMLConstants.ADDITIONAL_METADATA_LOCATION.get().equals(localPart)) {
-                throw logger.unsupportedType("AdditionalMetadataLocation");
-            } else if (JBossSAMLConstants.EXTENSIONS__PROTOCOL.get().equalsIgnoreCase(localPart)) {
-                entityDescriptorType.setExtensions(SAMLExtensionsParser.getInstance().parse(xmlEventReader));
-            } else if (JBossSAMLConstants.ROLE_DESCRIPTOR.get().equalsIgnoreCase(localPart)) {
-                RoleDescriptorType roleDescriptor = parseRoleDescriptor(xmlEventReader);
-
-                EntityDescriptorType.EDTDescriptorChoiceType edtDescChoice = new EntityDescriptorType.EDTDescriptorChoiceType(roleDescriptor);
-                EntityDescriptorType.EDTChoiceType edtChoice = EntityDescriptorType.EDTChoiceType.oneValue(edtDescChoice);
-
-                entityDescriptorType.addChoiceType(edtChoice);
-            } else
-                throw logger.parserUnknownStartElement(localPart, startElement.getLocation());
-        }
-        return entityDescriptorType;
+    private SAMLEntityDescriptorParser() {
+        super(ENTITY_DESCRIPTOR);
     }
 
-
-    private SPSSODescriptorType parseSPSSODescriptor(XMLEventReader xmlEventReader) throws ParsingException {
-        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-        StaxParserUtil.validate(startElement, JBossSAMLConstants.SP_SSO_DESCRIPTOR.get());
-
-        List<String> protocolEnum = parseProtocolEnumeration(startElement);
-        SPSSODescriptorType spSSODescriptor = new SPSSODescriptorType(protocolEnum);
-
-        Attribute wantAssertionsSigned = startElement.getAttributeByName(new QName(JBossSAMLConstants.WANT_ASSERTIONS_SIGNED
-                .get()));
-        if (wantAssertionsSigned != null) {
-            spSSODescriptor
-                    .setWantAssertionsSigned(Boolean.parseBoolean(StaxParserUtil.getAttributeValue(wantAssertionsSigned)));
-        }
-        Attribute wantAuthnSigned = startElement.getAttributeByName(new QName(JBossSAMLConstants.AUTHN_REQUESTS_SIGNED.get()));
-        if (wantAuthnSigned != null) {
-            spSSODescriptor.setAuthnRequestsSigned(Boolean.parseBoolean(StaxParserUtil.getAttributeValue(wantAuthnSigned)));
-        }
-
-        while (xmlEventReader.hasNext()) {
-            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
-            if (xmlEvent instanceof EndElement) {
-                EndElement end = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(end, JBossSAMLConstants.SP_SSO_DESCRIPTOR.get());
-                break;
-            }
-
-            startElement = (StartElement) xmlEvent;
-            String localPart = startElement.getName().getLocalPart();
-
-            if (JBossSAMLConstants.ARTIFACT_RESOLUTION_SERVICE.get().equals(localPart)) {
-                IndexedEndpointType endpoint = parseArtifactResolutionService(xmlEventReader, startElement);
-                spSSODescriptor.addArtifactResolutionService(endpoint);
-            } else if (JBossSAMLConstants.ASSERTION_CONSUMER_SERVICE.get().equals(localPart)) {
-                IndexedEndpointType endpoint = parseAssertionConsumerService(xmlEventReader, startElement);
-                spSSODescriptor.addAssertionConsumerService(endpoint);
-            } else if (JBossSAMLConstants.ATTRIBUTE_CONSUMING_SERVICE.get().equals(localPart)) {
-                AttributeConsumingServiceType attributeConsumer = parseAttributeConsumingService(xmlEventReader, startElement);
-                spSSODescriptor.addAttributeConsumerService(attributeConsumer);
-            } else if (JBossSAMLConstants.SINGLE_LOGOUT_SERVICE.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                EndpointType endpoint = getEndpointType(startElement);
-
-                EndElement endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(endElement, JBossSAMLConstants.SINGLE_LOGOUT_SERVICE.get());
-
-                spSSODescriptor.addSingleLogoutService(endpoint);
-            } else if (JBossSAMLConstants.MANAGE_NAMEID_SERVICE.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                EndpointType endpoint = getEndpointType(startElement);
-
-                EndElement endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(endElement, JBossSAMLConstants.MANAGE_NAMEID_SERVICE.get());
-
-                spSSODescriptor.addManageNameIDService(endpoint);
-            } else if (JBossSAMLConstants.NAMEID_FORMAT.get().equalsIgnoreCase(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                spSSODescriptor.addNameIDFormat(StaxParserUtil.getElementText(xmlEventReader));
-            } else if (JBossSAMLConstants.KEY_DESCRIPTOR.get().equalsIgnoreCase(localPart)) {
-                spSSODescriptor.addKeyDescriptor(parseKeyDescriptor(xmlEventReader));
-            } else if (JBossSAMLConstants.EXTENSIONS__PROTOCOL.get().equalsIgnoreCase(localPart)) {
-                spSSODescriptor.setExtensions(parseExtensions(xmlEventReader));
-            } else
-                throw logger.parserUnknownTag(localPart, startElement.getLocation());
-        }
-        return spSSODescriptor;
+    public static SAMLEntityDescriptorParser getInstance() {
+        return INSTANCE;
     }
 
-    private IDPSSODescriptorType parseIDPSSODescriptor(XMLEventReader xmlEventReader) throws ParsingException {
-        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-        StaxParserUtil.validate(startElement, JBossSAMLConstants.IDP_SSO_DESCRIPTOR.get());
-
-        List<String> protocolEnum = parseProtocolEnumeration(startElement);
-        IDPSSODescriptorType idpSSODescriptor = new IDPSSODescriptorType(protocolEnum);
-
-        Attribute wantAuthnSigned = startElement.getAttributeByName(new QName(JBossSAMLConstants.WANT_AUTHN_REQUESTS_SIGNED
-                .get()));
-        if (wantAuthnSigned != null) {
-            idpSSODescriptor
-                    .setWantAuthnRequestsSigned(Boolean.parseBoolean(StaxParserUtil.getAttributeValue(wantAuthnSigned)));
-        }
-
-        while (xmlEventReader.hasNext()) {
-            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
-            if (xmlEvent instanceof EndElement) {
-                EndElement end = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(end, JBossSAMLConstants.IDP_SSO_DESCRIPTOR.get());
-                break;
-            }
-
-            startElement = (StartElement) xmlEvent;
-            String localPart = startElement.getName().getLocalPart();
-
-            if (JBossSAMLConstants.ARTIFACT_RESOLUTION_SERVICE.get().equals(localPart)) {
-                IndexedEndpointType endpoint = parseArtifactResolutionService(xmlEventReader, startElement);
-                idpSSODescriptor.addArtifactResolutionService(endpoint);
-            } else if (JBossSAMLConstants.ASSERTION_ID_REQUEST_SERVICE.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                EndpointType endpoint = getEndpointType(startElement);
-
-                EndElement endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(endElement, JBossSAMLConstants.ASSERTION_ID_REQUEST_SERVICE.get());
-
-                idpSSODescriptor.addAssertionIDRequestService(endpoint);
-            } else if (JBossSAMLConstants.SINGLE_LOGOUT_SERVICE.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                EndpointType endpoint = getEndpointType(startElement);
-
-                EndElement endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(endElement, JBossSAMLConstants.SINGLE_LOGOUT_SERVICE.get());
-
-                idpSSODescriptor.addSingleLogoutService(endpoint);
-            } else if (JBossSAMLConstants.SINGLE_SIGNON_SERVICE.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                EndpointType endpoint = getEndpointType(startElement);
-
-                EndElement endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(endElement, JBossSAMLConstants.SINGLE_SIGNON_SERVICE.get());
-
-                idpSSODescriptor.addSingleSignOnService(endpoint);
-            } else if (JBossSAMLConstants.MANAGE_NAMEID_SERVICE.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                EndpointType endpoint = getEndpointType(startElement);
+    @Override
+    protected EntityDescriptorType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        String entityID = StaxParserUtil.getRequiredAttributeValue(element, SAMLMetadataQNames.ATTR_ENTITY_ID);
+        EntityDescriptorType descriptor = new EntityDescriptorType(entityID);
 
-                EndElement endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(endElement, JBossSAMLConstants.MANAGE_NAMEID_SERVICE.get());
+        descriptor.setValidUntil(StaxParserUtil.getXmlTimeAttributeValue(element, SAMLMetadataQNames.ATTR_VALID_UNTIL));
+        descriptor.setCacheDuration(StaxParserUtil.getXmlDurationAttributeValue(element, SAMLMetadataQNames.ATTR_CACHE_DURATION));
+        descriptor.setID(StaxParserUtil.getAttributeValue(element, SAMLMetadataQNames.ATTR_ID));
 
-                idpSSODescriptor.addManageNameIDService(endpoint);
-            } else if (JBossSAMLConstants.NAMEID_MAPPING_SERVICE.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                EndpointType endpoint = getEndpointType(startElement);
-
-                EndElement endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(endElement, JBossSAMLConstants.NAMEID_MAPPING_SERVICE.get());
-
-                idpSSODescriptor.addNameIDMappingService(endpoint);
-            } else if (JBossSAMLConstants.NAMEID_FORMAT.get().equalsIgnoreCase(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                idpSSODescriptor.addNameIDFormat(StaxParserUtil.getElementText(xmlEventReader));
-            } else if (JBossSAMLConstants.ATTRIBUTE.get().equalsIgnoreCase(localPart)) {
-                AttributeType attribute = SAMLAttributeParser.getInstance().parse(xmlEventReader);
-                idpSSODescriptor.addAttribute(attribute);
-            } else if (JBossSAMLConstants.KEY_DESCRIPTOR.get().equalsIgnoreCase(localPart)) {
-                idpSSODescriptor.addKeyDescriptor(parseKeyDescriptor(xmlEventReader));
-            } else if (JBossSAMLConstants.EXTENSIONS__PROTOCOL.get().equalsIgnoreCase(localPart)) {
-                idpSSODescriptor.setExtensions(parseExtensions(xmlEventReader));
-            } else
-                throw logger.parserUnknownTag(localPart, startElement.getLocation());
-        }
-        return idpSSODescriptor;
-    }
-
-    private EndpointType getEndpointType(StartElement startElement) {
-        Attribute bindingAttr = startElement.getAttributeByName(new QName(JBossSAMLConstants.BINDING.get()));
-        String binding = StaxParserUtil.getAttributeValue(bindingAttr);
-
-        Attribute locationAttr = startElement.getAttributeByName(new QName(JBossSAMLConstants.LOCATION.get()));
-        String location = StaxParserUtil.getAttributeValue(locationAttr);
-
-        EndpointType endpoint = new IndexedEndpointType(URI.create(binding), URI.create(location));
-        Attribute responseLocation = startElement.getAttributeByName(new QName(JBossSAMLConstants.RESPONSE_LOCATION.get()));
-        if (responseLocation != null) {
-            endpoint.setResponseLocation(URI.create(StaxParserUtil.getAttributeValue(responseLocation)));
-        }
-        return endpoint;
+        return descriptor;
     }
 
-    private AttributeAuthorityDescriptorType parseAttributeAuthorityDescriptor(XMLEventReader xmlEventReader)
-            throws ParsingException {
-        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-        StaxParserUtil.validate(startElement, JBossSAMLConstants.ATTRIBUTE_AUTHORITY_DESCRIPTOR.get());
-        List<String> protocolEnum = parseProtocolEnumeration(startElement);
-        AttributeAuthorityDescriptorType attributeAuthority = new AttributeAuthorityDescriptorType(protocolEnum);
-
-        while (xmlEventReader.hasNext()) {
-            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
-            if (xmlEvent instanceof EndElement) {
-                EndElement end = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(end, JBossSAMLConstants.ATTRIBUTE_AUTHORITY_DESCRIPTOR.get());
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, EntityDescriptorType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch (element) {
+            case SIGNATURE:
+                target.setSignature(StaxParserUtil.getDOMElement(xmlEventReader));
                 break;
-            }
-
-            startElement = (StartElement) xmlEvent;
-            String localPart = startElement.getName().getLocalPart();
-
-            if (JBossSAMLConstants.ATTRIBUTE_SERVICE.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                Attribute bindingAttr = startElement.getAttributeByName(new QName(JBossSAMLConstants.BINDING.get()));
-                String binding = StaxParserUtil.getAttributeValue(bindingAttr);
-
-                Attribute locationAttr = startElement.getAttributeByName(new QName(JBossSAMLConstants.LOCATION.get()));
-                String location = StaxParserUtil.getAttributeValue(locationAttr);
-
-                IndexedEndpointType endpoint = new IndexedEndpointType(URI.create(binding), URI.create(location));
-
-                EndElement endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(endElement, JBossSAMLConstants.ATTRIBUTE_SERVICE.get());
-
-                attributeAuthority.addAttributeService(endpoint);
-            } else if (JBossSAMLConstants.ASSERTION_ID_REQUEST_SERVICE.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                EndpointType endpoint = getEndpointType(startElement);
 
-                EndElement endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(endElement, JBossSAMLConstants.ASSERTION_ID_REQUEST_SERVICE.get());
-
-                attributeAuthority.addAssertionIDRequestService(endpoint);
-            } else if (JBossSAMLConstants.ATTRIBUTE_PROFILE.get().equalsIgnoreCase(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                attributeAuthority.addAttributeProfile(StaxParserUtil.getElementText(xmlEventReader));
-            } else if (JBossSAMLConstants.ATTRIBUTE.get().equalsIgnoreCase(localPart)) {
-                attributeAuthority.addAttribute(SAMLAttributeParser.getInstance().parse(xmlEventReader));
-            } else if (JBossSAMLConstants.KEY_DESCRIPTOR.get().equalsIgnoreCase(localPart)) {
-                attributeAuthority.addKeyDescriptor(parseKeyDescriptor(xmlEventReader));
-            } else if (JBossSAMLConstants.NAMEID_FORMAT.get().equalsIgnoreCase(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                attributeAuthority.addNameIDFormat(StaxParserUtil.getElementText(xmlEventReader));
-            } else if (JBossSAMLConstants.EXTENSIONS__PROTOCOL.get().equalsIgnoreCase(localPart)) {
-                attributeAuthority.setExtensions(parseExtensions(xmlEventReader));
-            } else
-                throw logger.parserUnknownTag(localPart, startElement.getLocation());
-
-        }
-        return attributeAuthority;
-    }
-
-    private OrganizationType parseOrganization(XMLEventReader xmlEventReader) throws ParsingException {
-        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-        StaxParserUtil.validate(startElement, JBossSAMLConstants.ORGANIZATION.get());
-
-        OrganizationType org = new OrganizationType();
-
-        while (xmlEventReader.hasNext()) {
-            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
-            if (xmlEvent instanceof EndElement) {
-                EndElement end = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(end, JBossSAMLConstants.ORGANIZATION.get());
+            case EXTENSIONS:
+                target.setExtensions(SAMLExtensionsParser.getInstance().parse(xmlEventReader));
                 break;
-            }
-
-            startElement = (StartElement) xmlEvent;
-            String localPart = startElement.getName().getLocalPart();
 
-            if (JBossSAMLConstants.ORGANIZATION_NAME.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                LocalizedNameType localName = getLocalizedName(xmlEventReader, startElement);
-                org.addOrganizationName(localName);
-            } else if (JBossSAMLConstants.ORGANIZATION_DISPLAY_NAME.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                LocalizedNameType localName = getLocalizedName(xmlEventReader, startElement);
-                org.addOrganizationDisplayName(localName);
-            } else if (JBossSAMLConstants.ORGANIZATION_URL.get().equals(localPart) ||
-              (JBossSAMLConstants.ORGANIZATION_URL_ALT.get().equals(localPart))) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                Attribute lang = startElement.getAttributeByName(new QName(JBossSAMLURIConstants.XML.get(), "lang"));
-                String langVal = StaxParserUtil.getAttributeValue(lang);
-                LocalizedURIType localName = new LocalizedURIType(langVal);
-                localName.setValue(URI.create(StaxParserUtil.getElementText(xmlEventReader)));
-                org.addOrganizationURL(localName);
-            } else if (JBossSAMLConstants.EXTENSIONS__PROTOCOL.get().equalsIgnoreCase(localPart)) {
-                org.setExtensions(parseExtensions(xmlEventReader));
-            } else
-                throw logger.parserUnknownTag(localPart, startElement.getLocation());
-        }
-        return org;
-    }
-
-    private KeyDescriptorType parseKeyDescriptor(XMLEventReader xmlEventReader) throws ParsingException {
-        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-        StaxParserUtil.validate(startElement, JBossSAMLConstants.KEY_DESCRIPTOR.get());
-
-        KeyDescriptorType keyDescriptor = new KeyDescriptorType();
+            case IDP_SSO_DESCRIPTOR:
+                {
+                    IDPSSODescriptorType idpSSO = SAMLIDPSSODescriptorParser.getInstance().parse(xmlEventReader);
 
-        String use = StaxParserUtil.getAttributeValue(startElement, "use");
-
-        if (use != null && !use.isEmpty()) {
-            keyDescriptor.setUse(KeyTypes.fromValue(use));
-        }
-
-        while (xmlEventReader.hasNext()) {
-            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
-            if (xmlEvent instanceof EndElement) {
-                EndElement end = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(end, JBossSAMLConstants.KEY_DESCRIPTOR.get());
+                    EntityDescriptorType.EDTDescriptorChoiceType edtDescChoice = new EntityDescriptorType.EDTDescriptorChoiceType(idpSSO);
+                    EntityDescriptorType.EDTChoiceType edtChoice = EntityDescriptorType.EDTChoiceType.oneValue(edtDescChoice);
+                    target.addChoiceType(edtChoice);
+                }
                 break;
-            }
-
-            startElement = (StartElement) xmlEvent;
-            String localPart = startElement.getName().getLocalPart();
 
-            if (JBossSAMLConstants.KEY_INFO.get().equals(localPart)) {
-                Element key = StaxParserUtil.getDOMElement(xmlEventReader);
-                keyDescriptor.setKeyInfo(key);
-            } else if (JBossSAMLConstants.ENCRYPTION_METHOD.get().equals(localPart)) {
-                keyDescriptor.addEncryptionMethod(parseEncryptionMethod(xmlEventReader));
-            } else
-                throw logger.parserUnknownTag(localPart, startElement.getLocation());
-        }
-
-        return keyDescriptor;
-    }
-
-    private EncryptionMethodType parseEncryptionMethod(XMLEventReader xmlEventReader) throws ParsingException {
-        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-        StaxParserUtil.validate(startElement, JBossSAMLConstants.ENCRYPTION_METHOD.get());
-        Attribute algorithm = startElement.getAttributeByName(new QName("Algorithm"));
-        EncryptionMethodType encryptionMethodType = new EncryptionMethodType(algorithm.getValue());
+            case SP_SSO_DESCRIPTOR:
+                {
+                    SPSSODescriptorType spSSO = SAMLSPSSODescriptorParser.getInstance().parse(xmlEventReader);
 
-        BigInteger keySize = null;
-        byte[] OAEPparams = null;
-
-        while (xmlEventReader.hasNext()) {
-            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
-            if (xmlEvent instanceof EndElement) {
-                EndElement end = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(end, JBossSAMLConstants.ENCRYPTION_METHOD.get());
+                    EntityDescriptorType.EDTDescriptorChoiceType edtDescChoice = new EntityDescriptorType.EDTDescriptorChoiceType(spSSO);
+                    EntityDescriptorType.EDTChoiceType edtChoice = EntityDescriptorType.EDTChoiceType.oneValue(edtDescChoice);
+                    target.addChoiceType(edtChoice);
+                }
                 break;
-            }
-
-            startElement = (StartElement) xmlEvent;
-            String localPart = startElement.getName().getLocalPart();
-
-            if ("KeySize".equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                keySize = BigInteger.valueOf(Long.valueOf(StaxParserUtil.getElementText(xmlEventReader)));
-            } else if ("OAEPparams".equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                OAEPparams = StaxParserUtil.getElementText(xmlEventReader).getBytes(GeneralConstants.SAML_CHARSET);
-            } else {
-                throw logger.parserUnknownTag(localPart, startElement.getLocation());
-            }
-        }
 
-        EncryptionMethodType.EncryptionMethod encryptionMethod = new EncryptionMethodType.EncryptionMethod(keySize, OAEPparams);
+            case ATTRIBUTE_AUTHORITY_DESCRIPTOR:
+                {
+                    AttributeAuthorityDescriptorType attrAuthority = SAMLAttributeAuthorityDescriptorParser.getInstance().parse(xmlEventReader);
 
-        encryptionMethodType.setEncryptionMethod(encryptionMethod);
-
-        return encryptionMethodType;
-    }
-
-    private ContactType parseContactPerson(XMLEventReader xmlEventReader) throws ParsingException {
-        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-        StaxParserUtil.validate(startElement, JBossSAMLConstants.CONTACT_PERSON.get());
-
-        Attribute attr = startElement.getAttributeByName(new QName(JBossSAMLConstants.CONTACT_TYPE.get()));
-        if (attr == null)
-            throw logger.parserRequiredAttribute("contactType");
-        ContactType contactType = new ContactType(ContactTypeType.fromValue(StaxParserUtil.getAttributeValue(attr)));
-
-        while (xmlEventReader.hasNext()) {
-            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
-            if (xmlEvent instanceof EndElement) {
-                EndElement end = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(end, JBossSAMLConstants.CONTACT_PERSON.get());
+                    EntityDescriptorType.EDTDescriptorChoiceType edtDescChoice = new EntityDescriptorType.EDTDescriptorChoiceType(attrAuthority);
+                    EntityDescriptorType.EDTChoiceType edtChoice = EntityDescriptorType.EDTChoiceType.oneValue(edtDescChoice);
+                    target.addChoiceType(edtChoice);
+                }
                 break;
-            }
-
-            startElement = (StartElement) xmlEvent;
-            String localPart = startElement.getName().getLocalPart();
 
-            if (JBossSAMLConstants.COMPANY.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                contactType.setCompany(StaxParserUtil.getElementText(xmlEventReader));
-            } else if (JBossSAMLConstants.GIVEN_NAME.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                contactType.setGivenName(StaxParserUtil.getElementText(xmlEventReader));
-            } else if (JBossSAMLConstants.SURNAME.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                contactType.setSurName(StaxParserUtil.getElementText(xmlEventReader));
-            } else if (JBossSAMLConstants.EMAIL_ADDRESS.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                contactType.addEmailAddress(StaxParserUtil.getElementText(xmlEventReader));
-            } else if (JBossSAMLConstants.TELEPHONE_NUMBER.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                contactType.addTelephone(StaxParserUtil.getElementText(xmlEventReader));
-            } else if (JBossSAMLConstants.EXTENSIONS__PROTOCOL.get().equalsIgnoreCase(localPart)) {
-                contactType.setExtensions(parseExtensions(xmlEventReader));
-            } else
-                throw logger.parserUnknownTag(localPart, startElement.getLocation());
-        }
-        return contactType;
-    }
+            case AUTHN_AUTHORITY_DESCRIPTOR:
+                {
+                    AuthnAuthorityDescriptorType authAuthority = SAMLAuthnAuthorityDescriptorParser.getInstance().parse(xmlEventReader);
 
-    private LocalizedNameType getLocalizedName(XMLEventReader xmlEventReader, StartElement startElement)
-            throws ParsingException {
-        Attribute lang = startElement.getAttributeByName(new QName(JBossSAMLURIConstants.XML.get(), "lang"));
-        String langVal = StaxParserUtil.getAttributeValue(lang);
-        LocalizedNameType localName = new LocalizedNameType(langVal);
-        localName.setValue(StaxParserUtil.getElementText(xmlEventReader));
-        return localName;
-    }
-
-    private IndexedEndpointType parseAssertionConsumerService(XMLEventReader xmlEventReader, StartElement startElement)
-            throws ParsingException {
-        startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-        IndexedEndpointType endpoint = parseIndexedEndpoint(xmlEventReader, startElement);
-
-        EndElement endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
-        StaxParserUtil.validate(endElement, JBossSAMLConstants.ASSERTION_CONSUMER_SERVICE.get());
-
-        return endpoint;
-    }
-
-    private IndexedEndpointType parseArtifactResolutionService(XMLEventReader xmlEventReader, StartElement startElement)
-            throws ParsingException {
-        startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-        IndexedEndpointType endpoint = parseIndexedEndpoint(xmlEventReader, startElement);
-
-        EndElement endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
-        StaxParserUtil.validate(endElement, JBossSAMLConstants.ARTIFACT_RESOLUTION_SERVICE.get());
-
-        return endpoint;
-    }
-
-    private IndexedEndpointType parseIndexedEndpoint(XMLEventReader xmlEventReader, StartElement startElement) {
-        Attribute bindingAttr = startElement.getAttributeByName(new QName(JBossSAMLConstants.BINDING.get()));
-        String binding = StaxParserUtil.getAttributeValue(bindingAttr);
-
-        Attribute locationAttr = startElement.getAttributeByName(new QName(JBossSAMLConstants.LOCATION.get()));
-        String location = StaxParserUtil.getAttributeValue(locationAttr);
-
-        IndexedEndpointType endpoint = new IndexedEndpointType(URI.create(binding), URI.create(location));
-        Attribute isDefault = startElement.getAttributeByName(new QName(JBossSAMLConstants.ISDEFAULT.get()));
-        if (isDefault != null) {
-            endpoint.setIsDefault(Boolean.parseBoolean(StaxParserUtil.getAttributeValue(isDefault)));
-        }
-        Attribute index = startElement.getAttributeByName(new QName(JBossSAMLConstants.INDEX.get()));
-        if (index != null) {
-            endpoint.setIndex(Integer.parseInt(StaxParserUtil.getAttributeValue(index)));
-        }
-        return endpoint;
-    }
-
-    private AttributeConsumingServiceType parseAttributeConsumingService(XMLEventReader xmlEventReader,
-                                                                         StartElement startElement) throws ParsingException {
-        startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-
-        Attribute indexAttr = startElement.getAttributeByName(new QName(JBossSAMLConstants.INDEX.get()));
-        if (indexAttr == null)
-            throw logger.parserRequiredAttribute("index");
-
-        AttributeConsumingServiceType attributeConsumer = new AttributeConsumingServiceType(Integer.parseInt(StaxParserUtil
-                .getAttributeValue(indexAttr)));
-        while (xmlEventReader.hasNext()) {
-            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
-            if (xmlEvent instanceof EndElement) {
-                EndElement end = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(end, JBossSAMLConstants.ATTRIBUTE_CONSUMING_SERVICE.get());
+                    EntityDescriptorType.EDTDescriptorChoiceType edtDescChoice = new EntityDescriptorType.EDTDescriptorChoiceType(authAuthority);
+                    EntityDescriptorType.EDTChoiceType edtChoice = EntityDescriptorType.EDTChoiceType.oneValue(edtDescChoice);
+                    target.addChoiceType(edtChoice);
+                }
                 break;
-            }
-
-            startElement = (StartElement) xmlEvent;
-            String localPart = startElement.getName().getLocalPart();
-
-            if (JBossSAMLConstants.SERVICE_NAME.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                LocalizedNameType localName = getLocalizedName(xmlEventReader, startElement);
-                attributeConsumer.addServiceName(localName);
-            } else if (JBossSAMLConstants.SERVICE_DESCRIPTION.get().equals(localPart)) {
-                startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-                LocalizedNameType localName = getLocalizedName(xmlEventReader, startElement);
-                attributeConsumer.addServiceDescription(localName);
-            } else if (JBossSAMLConstants.REQUESTED_ATTRIBUTE.get().equals(localPart)) {
-                attributeConsumer.addRequestedAttribute(SAMLRequestedAttributeParser.getInstance().parse(xmlEventReader));
-            } else
-                throw logger.parserUnknownTag(localPart, startElement.getLocation());
-        }
-
-        return attributeConsumer;
-    }
 
-    private ExtensionsType parseExtensions(XMLEventReader xmlEventReader) throws ParsingException {
-        ExtensionsType extensions = new ExtensionsType();
-        Element extElement = StaxParserUtil.getDOMElement(xmlEventReader);
-        extensions.setElement(extElement);
-        return extensions;
-    }
+            case PDP_DESCRIPTOR:
+                {
+                    PDPDescriptorType pdpDescriptor = SAMLPDPDescriptorParser.getInstance().parse(xmlEventReader);
 
-    private RoleDescriptorType parseRoleDescriptor(XMLEventReader xmlEventReader) throws ParsingException {
-        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
-        StaxParserUtil.validate(startElement, JBossSAMLConstants.ROLE_DESCRIPTOR.get());
-        List<String> protocolEnum = parseProtocolEnumeration(startElement);
-        RoleDescriptorType roleDescriptorType = new RoleDescriptorType(protocolEnum) {};
-
-        while (xmlEventReader.hasNext()) {
-            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
-            if (xmlEvent instanceof EndElement) {
-                EndElement end = StaxParserUtil.getNextEndElement(xmlEventReader);
-                StaxParserUtil.validate(end, JBossSAMLConstants.ROLE_DESCRIPTOR.get());
+                    EntityDescriptorType.EDTDescriptorChoiceType edtDescChoice = new EntityDescriptorType.EDTDescriptorChoiceType(pdpDescriptor);
+                    EntityDescriptorType.EDTChoiceType edtChoice = EntityDescriptorType.EDTChoiceType.oneValue(edtDescChoice);
+                    target.addChoiceType(edtChoice);
+                }
+                break;
+            case ROLE_DESCRIPTOR:
+            case AFFILIATION_DESCRIPTOR:
+            case ADDITIONAL_METADATA_LOCATION:
+                StaxParserUtil.bypassElementBlock(xmlEventReader);
                 break;
-            }
-
-            startElement = (StartElement) xmlEvent;
-            String localPart = startElement.getName().getLocalPart();
 
-            if (JBossSAMLConstants.KEY_DESCRIPTOR.get().equalsIgnoreCase(localPart)) {
-                KeyDescriptorType keyDescriptor = parseKeyDescriptor(xmlEventReader);
-                roleDescriptorType.addKeyDescriptor(keyDescriptor);
-            } else {
-                StaxParserUtil.bypassElementBlock(xmlEventReader, localPart);
-            }
-        }
+            case ORGANIZATION:
+                target.setOrganization(SAMLOrganizationParser.getInstance().parse(xmlEventReader));
+                break;
 
-        return roleDescriptorType;
-    }
+            case CONTACT_PERSON:
+                target.addContactPerson(SAMLContactPersonParser.getInstance().parse(xmlEventReader));
+                break;
 
-    /**
-     * Parse a space delimited list of strings
-     *
-     * @param startElement
-     *
-     * @return
-     */
-    public static List<String> parseProtocolEnumeration(StartElement startElement) {
-        List<String> protocolEnum = new ArrayList<>();
-        Attribute proto = startElement.getAttributeByName(JBossSAMLConstants.PROTOCOL_SUPPORT_ENUMERATION.getAsQName());
-        String val = StaxParserUtil.getAttributeValue(proto);
-        if (StringUtil.isNotNull(val)) {
-            StringTokenizer st = new StringTokenizer(val);
-            while (st.hasMoreTokens()) {
-                protocolEnum.add(st.nextToken());
-            }
+            default:
+                throw LOGGER.parserUnknownTag(StaxParserUtil.getElementName(elementDetail), elementDetail.getLocation());
 
         }
-        return protocolEnum;
     }
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLExtensionsParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLExtensionsParser.java
index 585c88f..4dc8ab5 100644
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLExtensionsParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLExtensionsParser.java
@@ -16,24 +16,24 @@
  */
 package org.keycloak.saml.processing.core.parsers.saml.metadata;
 
-import javax.xml.stream.XMLEventReader;
-import javax.xml.stream.events.StartElement;
 import org.keycloak.dom.saml.v2.metadata.ExtensionsType;
-import org.keycloak.saml.common.constants.JBossSAMLConstants;
 import org.keycloak.saml.common.exceptions.ParsingException;
 import org.keycloak.saml.common.util.StaxParserUtil;
 
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+
 /**
  * Parses &lt;samlp:Extensions&gt; SAML2 element into series of DOM nodes.
  *
  * @author hmlnarik
  */
-public class SAMLExtensionsParser extends AbstractStaxSamlParser<ExtensionsType> {
+public class SAMLExtensionsParser extends AbstractStaxSamlMetadataParser<ExtensionsType> {
 
     private static final SAMLExtensionsParser INSTANCE = new SAMLExtensionsParser();
 
     private SAMLExtensionsParser() {
-        super(JBossSAMLConstants.EXTENSIONS__METADATA);
+        super(SAMLMetadataQNames.EXTENSIONS);
     }
 
     public static SAMLExtensionsParser getInstance() {
@@ -46,7 +46,7 @@ public class SAMLExtensionsParser extends AbstractStaxSamlParser<ExtensionsType>
     }
 
     @Override
-    protected void processSubElement(XMLEventReader xmlEventReader, ExtensionsType target, JBossSAMLConstants element, StartElement elementDetail) throws ParsingException {
-        target.setElement(StaxParserUtil.getDOMElement(xmlEventReader));
+    protected void processSubElement(XMLEventReader xmlEventReader, ExtensionsType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        target.addExtension(StaxParserUtil.getDOMElement(xmlEventReader));
     }
 }
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLIDPSSODescriptorParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLIDPSSODescriptorParser.java
new file mode 100644
index 0000000..f90b7b6
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLIDPSSODescriptorParser.java
@@ -0,0 +1,73 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.metadata.IDPSSODescriptorType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+import java.util.List;
+
+import static org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLMetadataQNames.IDP_SSO_DESCRIPTOR;
+
+/**
+ * @author mhajas
+ */
+public class SAMLIDPSSODescriptorParser extends SAMLSSODescriptorTypeParser<IDPSSODescriptorType> {
+
+    private static final SAMLIDPSSODescriptorParser INSTANCE = new SAMLIDPSSODescriptorParser();
+
+    private SAMLIDPSSODescriptorParser() {
+        super(IDP_SSO_DESCRIPTOR);
+    }
+
+    public static SAMLIDPSSODescriptorParser getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    protected IDPSSODescriptorType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        List<String> protocolEnum = StaxParserUtil.getRequiredStringListAttributeValue(element, SAMLMetadataQNames.ATTR_PROTOCOL_SUPPORT_ENUMERATION);
+        IDPSSODescriptorType descriptor = new IDPSSODescriptorType(protocolEnum);
+
+        // Role descriptor optional arguments
+        parseOptionalArguments(element, descriptor);
+
+        // IDPSSODecsriptor optional attributes
+        Boolean wantAuthnRequestsSigned = StaxParserUtil.getBooleanAttributeValue(element, SAMLMetadataQNames.ATTR_WANT_AUTHN_REQUESTS_SIGNED);
+        if (wantAuthnRequestsSigned != null) {
+            descriptor.setWantAuthnRequestsSigned(wantAuthnRequestsSigned);
+        }
+
+        return descriptor;
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, IDPSSODescriptorType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch (element) {
+            case SINGLE_SIGNON_SERVICE:
+                target.addSingleSignOnService(SAMLSingleSignOnServiceParser.getInstance().parse(xmlEventReader));
+                break;
+
+            case NAMEID_MAPPING_SERVICE:
+                target.addNameIDMappingService(SAMLNameIDMappingServiceParser.getInstance().parse(xmlEventReader));
+                break;
+
+            case ASSERTION_ID_REQUEST_SERVICE:
+                target.addAssertionIDRequestService(SAMLAssertinIDRequestServiceParser.getInstance().parse(xmlEventReader));
+                break;
+
+            case ATTRIBUTE_PROFILE:
+                StaxParserUtil.advance(xmlEventReader);
+                target.addAttributeProfile(StaxParserUtil.getElementText(xmlEventReader));
+                break;
+
+            case ATTRIBUTE:
+                target.addAttribute(SAMLAttributeParser.getInstance().parse(xmlEventReader));
+                break;
+
+            default:
+                super.processSubElement(xmlEventReader, target, element, elementDetail);
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLIndexedEndpointTypeParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLIndexedEndpointTypeParser.java
new file mode 100644
index 0000000..68b5e17
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLIndexedEndpointTypeParser.java
@@ -0,0 +1,50 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.metadata.IndexedEndpointType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+import java.net.URI;
+
+/**
+ * @author mhajas
+ */
+public abstract class SAMLIndexedEndpointTypeParser extends AbstractStaxSamlMetadataParser<IndexedEndpointType> {
+
+    public SAMLIndexedEndpointTypeParser(SAMLMetadataQNames expectedStartElement) {
+        super(expectedStartElement);
+    }
+
+    @Override
+    protected IndexedEndpointType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        String binding = StaxParserUtil.getRequiredAttributeValue(element, SAMLMetadataQNames.ATTR_BINDING);
+        String location = StaxParserUtil.getRequiredAttributeValue(element, SAMLMetadataQNames.ATTR_LOCATION);
+
+        IndexedEndpointType endpoint = new IndexedEndpointType(URI.create(binding), URI.create(location));
+
+        Boolean isDefault = StaxParserUtil.getBooleanAttributeValue(element, SAMLMetadataQNames.ATTR_IS_DEFAULT);
+        if (isDefault != null) {
+            endpoint.setIsDefault(isDefault);
+        }
+        
+        Integer index = StaxParserUtil.getIntegerAttributeValue(element, SAMLMetadataQNames.ATTR_INDEX);
+        if (index != null)
+            endpoint.setIndex(index);
+
+        // EndpointType attributes
+        String responseLocation = StaxParserUtil.getAttributeValue(element, SAMLMetadataQNames.ATTR_RESPONSE_LOCATION);
+
+        if (responseLocation != null) {
+            endpoint.setResponseLocation(URI.create(responseLocation));
+        }
+
+        return endpoint;
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, IndexedEndpointType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        throw LOGGER.parserUnknownTag(StaxParserUtil.getElementName(elementDetail), elementDetail.getLocation());
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLKeyDescriptorParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLKeyDescriptorParser.java
new file mode 100644
index 0000000..3e435ff
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLKeyDescriptorParser.java
@@ -0,0 +1,56 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+
+import static org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLMetadataQNames.KEY_DESCRIPTOR;
+
+/**
+ * @author mhajas
+ */
+public class SAMLKeyDescriptorParser extends AbstractStaxSamlMetadataParser<KeyDescriptorType> {
+
+    private static final SAMLKeyDescriptorParser INSTANCE = new SAMLKeyDescriptorParser();
+
+    public SAMLKeyDescriptorParser() {
+        super(KEY_DESCRIPTOR);
+    }
+
+    public static SAMLKeyDescriptorParser getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    protected KeyDescriptorType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        KeyDescriptorType keyDescriptor = new KeyDescriptorType();
+
+        String use = StaxParserUtil.getAttributeValue(element, SAMLMetadataQNames.ATTR_USE);
+
+        if (use != null && !use.isEmpty()) {
+            keyDescriptor.setUse(KeyTypes.fromValue(use));
+        }
+
+        return keyDescriptor;
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, KeyDescriptorType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch(element) {
+            case KEY_INFO:
+                target.setKeyInfo(StaxParserUtil.getDOMElement(xmlEventReader));
+                break;
+
+            case ENCRYPTION_METHOD:
+                target.addEncryptionMethod(SAMLEncryptionMethodParser.getInstance().parse(xmlEventReader));
+                break;
+
+            default:
+                throw LOGGER.parserUnknownTag(StaxParserUtil.getElementName(elementDetail), elementDetail.getLocation());
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLManageNameIDServiceParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLManageNameIDServiceParser.java
new file mode 100644
index 0000000..715f28c
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLManageNameIDServiceParser.java
@@ -0,0 +1,17 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+/**
+ * @author mhajas
+ */
+public class SAMLManageNameIDServiceParser extends SAMLEndpointTypeParser {
+
+    private static final SAMLManageNameIDServiceParser INSTANCE = new SAMLManageNameIDServiceParser();
+
+    public SAMLManageNameIDServiceParser() {
+        super(SAMLMetadataQNames.MANAGE_NAMEID_SERVICE);
+    }
+
+    public static SAMLManageNameIDServiceParser getInstance() {
+        return INSTANCE;
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLMetadataQNames.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLMetadataQNames.java
new file mode 100644
index 0000000..250748a
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLMetadataQNames.java
@@ -0,0 +1,113 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.processing.core.parsers.saml.assertion.SAMLAssertionQNames;
+import org.keycloak.saml.processing.core.parsers.saml.xmldsig.XmlDSigQNames;
+import org.keycloak.saml.processing.core.parsers.util.HasQName;
+
+import javax.xml.namespace.QName;
+
+
+/**
+ * @author mhajas
+ */
+public enum SAMLMetadataQNames implements HasQName {
+    ADDITIONAL_METADATA_LOCATION("AdditionalMetadataLocation"),
+    AFFILIATE_MEMBER("AffiliateMember"),
+    AFFILIATION_DESCRIPTOR("AffiliationDescriptor"),
+    ARTIFACT_RESOLUTION_SERVICE("ArtifactResolutionService"),
+    ASSERTION_CONSUMER_SERVICE("AssertionConsumerService"),
+    ASSERTION_ID_REQUEST_SERVICE("AssertionIDRequestService"),
+    ATTRIBUTE_AUTHORITY_DESCRIPTOR("AttributeAuthorityDescriptor"),
+    ATTRIBUTE_CONSUMING_SERVICE("AttributeConsumingService"),
+    ATTRIBUTE_PROFILE("AttributeProfile"),
+    ATTRIBUTE_SERVICE("AttributeService"),
+    ATTRIBUTE_VALUE("AttributeValue"),
+    AUTHN_AUTHORITY_DESCRIPTOR("AuthnAuthorityDescriptor"),
+    AUTHN_QUERY_SERVICE("AuthnQueryService"),
+    AUTHZ_SERVICE("AuthzService"),
+    COMPANY("Company"),
+    CONTACT_PERSON("ContactPerson"),
+    EMAIL_ADDRESS("EmailAddress"),
+    ENCRYPTION_METHOD("EncryptionMethod"),
+    ENTITIES_DESCRIPTOR("EntitiesDescriptor"),
+    ENTITY_DESCRIPTOR("EntityDescriptor"),
+    EXTENSIONS("Extensions"),
+    GIVEN_NAME("GivenName"),
+    IDP_SSO_DESCRIPTOR("IDPSSODescriptor"),
+    KEY_DESCRIPTOR("KeyDescriptor"),
+    MANAGE_NAMEID_SERVICE("ManageNameIDService"),
+    NAMEID_FORMAT("NameIDFormat"),
+    NAMEID_MAPPING_SERVICE("NameIDMappingService"),
+    ORGANIZATION_DISPLAY_NAME("OrganizationDisplayName"),
+    ORGANIZATION_NAME("OrganizationName"),
+    ORGANIZATION("Organization"),
+    ORGANIZATION_URL("OrganizationURL"),
+    ORGANIZATION_URL_ALT("OrganizationUrl"),    // non-standard: KEYCLOAK-4040,
+    PDP_DESCRIPTOR("PDPDescriptor"),
+    REQUESTED_ATTRIBUTE("RequestedAttribute"),
+    ROLE_DESCRIPTOR("RoleDescriptor"),
+    SERVICE_DESCRIPTION("ServiceDescription"),
+    SERVICE_NAME("ServiceName"),
+    SINGLE_LOGOUT_SERVICE("SingleLogoutService"),
+    SINGLE_SIGNON_SERVICE("SingleSignOnService"),
+    SP_SSO_DESCRIPTOR("SPSSODescriptor"),
+    SURNAME("SurName"),
+    TELEPHONE_NUMBER("TelephoneNumber"),
+
+    // Attribute names
+    ATTR_ENTITY_ID(null, "entityID"),
+    ATTR_ID(null, "ID"),
+    ATTR_VALID_UNTIL(null, "validUntil"),
+    ATTR_CACHE_DURATION(null, "cacheDuration"),
+    ATTR_PROTOCOL_SUPPORT_ENUMERATION(null, "protocolSupportEnumeration"),
+    ATTR_USE(null, "use"),
+    ATTR_ALGORITHM(null, "Algorithm"),
+    ATTR_LANG(JBossSAMLURIConstants.XML, "lang"),
+    ATTR_CONTACT_TYPE(null, "contactType"),
+    ATTR_AUTHN_REQUESTS_SIGNED(null, "AuthnRequestsSigned"),
+    ATTR_WANT_ASSERTIONS_SIGNED(null, "WantAssertionsSigned"),
+    ATTR_WANT_AUTHN_REQUESTS_SIGNED(null, "WantAuthnRequestsSigned"),
+    ATTR_BINDING(null, "Binding"),
+    ATTR_LOCATION(null, "Location"),
+    ATTR_IS_DEFAULT(null, "isDefault"),
+    ATTR_INDEX(null, "index"),
+    ATTR_RESPONSE_LOCATION(null, "ResponseLocation"),
+    ATTR_FRIENDLY_NAME(null, "FriendlyName"),
+    ATTR_IS_REQUIRED(null, "isRequired"),
+    ATTR_NAME(null, "Name"),
+    ATTR_NAME_FORMAT(null, "NameFormat"),
+
+    // Elements from other namespaces that can be direct subelements of this namespace's elements
+    SIGNATURE(XmlDSigQNames.SIGNATURE),
+    KEY_INFO(XmlDSigQNames.KEY_INFO),
+    KEY_SIZE(JBossSAMLURIConstants.XMLENC_NSURI, "KeySize"),
+    OAEP_PARAMS(JBossSAMLURIConstants.XMLENC_NSURI, "OAEPparams"),
+    ATTR_X500_ENCODING(JBossSAMLURIConstants.X500_NSURI, "Encoding"),
+    ATTRIBUTE(SAMLAssertionQNames.ATTRIBUTE),
+
+    UNKNOWN_ELEMENT("");
+
+    private final QName qName;
+
+    private SAMLMetadataQNames(String localName) {
+        this.qName = new QName(JBossSAMLURIConstants.METADATA_NSURI.get(), localName);
+    }
+
+    private SAMLMetadataQNames(HasQName source) {
+        this.qName = source.getQName();
+    }
+
+    private SAMLMetadataQNames(JBossSAMLURIConstants nsUri, String localName) {
+        this.qName = new QName(nsUri == null ? null : nsUri.get(), localName);
+    }
+
+    @Override
+    public QName getQName() {
+        return qName;
+    }
+
+    public QName getQName(String prefix) {
+        return new QName(this.qName.getNamespaceURI(), this.qName.getLocalPart(), prefix);
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLNameIDMappingServiceParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLNameIDMappingServiceParser.java
new file mode 100644
index 0000000..c978fbe
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLNameIDMappingServiceParser.java
@@ -0,0 +1,17 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+/**
+ * @author mhajas
+ */
+public class SAMLNameIDMappingServiceParser extends SAMLEndpointTypeParser {
+
+    private static final SAMLNameIDMappingServiceParser INSTANCE = new SAMLNameIDMappingServiceParser();
+
+    public SAMLNameIDMappingServiceParser() {
+        super(SAMLMetadataQNames.NAMEID_MAPPING_SERVICE);
+    }
+
+    public static SAMLNameIDMappingServiceParser getInstance() {
+        return INSTANCE;
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLOrganizationParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLOrganizationParser.java
new file mode 100644
index 0000000..da3e227
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLOrganizationParser.java
@@ -0,0 +1,68 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.metadata.LocalizedNameType;
+import org.keycloak.dom.saml.v2.metadata.LocalizedURIType;
+import org.keycloak.dom.saml.v2.metadata.OrganizationType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+import java.net.URI;
+
+import static org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLMetadataQNames.ATTR_LANG;
+import static org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLMetadataQNames.ORGANIZATION;
+
+/**
+ * @author mhajas
+ */
+public class SAMLOrganizationParser extends AbstractStaxSamlMetadataParser<OrganizationType> {
+
+    private static final SAMLOrganizationParser INSTANCE = new SAMLOrganizationParser();
+
+    public SAMLOrganizationParser() {
+        super(ORGANIZATION);
+    }
+
+    public static SAMLOrganizationParser getInstance() {
+        return INSTANCE;
+    }
+    @Override
+    protected OrganizationType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        return new OrganizationType();
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, OrganizationType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch (element) {
+            case ORGANIZATION_NAME:
+                LocalizedNameType orgName = new LocalizedNameType(StaxParserUtil.getAttributeValue(elementDetail, ATTR_LANG));
+                StaxParserUtil.advance(xmlEventReader);
+                orgName.setValue(StaxParserUtil.getElementText(xmlEventReader));
+                target.addOrganizationName(orgName);
+                break;
+
+            case ORGANIZATION_DISPLAY_NAME:
+                LocalizedNameType orgDispName = new LocalizedNameType(StaxParserUtil.getAttributeValue(elementDetail, ATTR_LANG));
+                StaxParserUtil.advance(xmlEventReader);
+                orgDispName.setValue(StaxParserUtil.getElementText(xmlEventReader));
+                target.addOrganizationDisplayName(orgDispName);
+                break;
+
+            case ORGANIZATION_URL:
+            case ORGANIZATION_URL_ALT:
+                LocalizedURIType orgURL = new LocalizedURIType(StaxParserUtil.getAttributeValue(elementDetail, ATTR_LANG));
+                StaxParserUtil.advance(xmlEventReader);
+                orgURL.setValue(URI.create(StaxParserUtil.getElementText(xmlEventReader)));
+                target.addOrganizationURL(orgURL);
+                break;
+
+            case EXTENSIONS:
+                target.setExtensions(SAMLExtensionsParser.getInstance().parse(xmlEventReader));
+                break;
+
+            default:
+                throw LOGGER.parserUnknownTag(StaxParserUtil.getElementName(elementDetail), elementDetail.getLocation());
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLPDPDescriptorParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLPDPDescriptorParser.java
new file mode 100644
index 0000000..07239e6
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLPDPDescriptorParser.java
@@ -0,0 +1,56 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.metadata.PDPDescriptorType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+import java.util.List;
+
+/**
+ * @author mhajas
+ */
+public class SAMLPDPDescriptorParser extends SAMLRoleDecriptorTypeParser<PDPDescriptorType> {
+
+    private static final SAMLPDPDescriptorParser INSTANCE = new SAMLPDPDescriptorParser();
+
+    public SAMLPDPDescriptorParser() {
+        super(SAMLMetadataQNames.PDP_DESCRIPTOR);
+    }
+
+    public static SAMLPDPDescriptorParser getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    protected PDPDescriptorType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        List<String> protocolEnum = StaxParserUtil.getRequiredStringListAttributeValue(element, SAMLMetadataQNames.ATTR_PROTOCOL_SUPPORT_ENUMERATION);
+        PDPDescriptorType descriptor = new PDPDescriptorType(protocolEnum);
+
+        parseOptionalArguments(element, descriptor);
+
+        return descriptor;
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, PDPDescriptorType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch (element) {
+            case AUTHZ_SERVICE:
+            target.addAuthZService(SAMLAuthzServiceParser.getInstance().parse(xmlEventReader));
+            break;
+
+        case ASSERTION_ID_REQUEST_SERVICE:
+            target.addAssertionIDRequestService(SAMLAssertinIDRequestServiceParser.getInstance().parse(xmlEventReader));
+            break;
+
+        case NAMEID_FORMAT:
+            StaxParserUtil.advance(xmlEventReader);
+            target.addNameIDFormat(StaxParserUtil.getElementText(xmlEventReader));
+            break;
+
+        default:
+            super.processSubElement(xmlEventReader, target, element, elementDetail);
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLRequestedAttributeParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLRequestedAttributeParser.java
index b1dbb7d..77543eb 100644
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLRequestedAttributeParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLRequestedAttributeParser.java
@@ -17,31 +17,25 @@
 package org.keycloak.saml.processing.core.parsers.saml.metadata;
 
 import org.keycloak.dom.saml.v2.metadata.RequestedAttributeType;
-import org.keycloak.saml.common.constants.JBossSAMLConstants;
-import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.common.exceptions.ParsingException;
 import org.keycloak.saml.common.util.StaxParserUtil;
-import javax.xml.namespace.QName;
+import org.keycloak.saml.processing.core.parsers.saml.assertion.SAMLAssertionQNames;
+import org.keycloak.saml.processing.core.parsers.saml.assertion.SAMLAttributeValueParser;
+
 import javax.xml.stream.XMLEventReader;
-import javax.xml.stream.events.Attribute;
 import javax.xml.stream.events.StartElement;
-import org.keycloak.saml.processing.core.parsers.saml.assertion.SAMLAttributeValueParser;
-import org.keycloak.saml.processing.core.parsers.saml.assertion.SAMLAssertionQNames;
 
 /**
  * Parse the <conditions> in the saml assertion
  *
  * @since Oct 14, 2010
  */
-public class SAMLRequestedAttributeParser extends AbstractStaxSamlParser<RequestedAttributeType> {
+public class SAMLRequestedAttributeParser extends AbstractStaxSamlMetadataParser<RequestedAttributeType> {
 
     private static final SAMLRequestedAttributeParser INSTANCE = new SAMLRequestedAttributeParser();
-    private static final QName X500_ENCODING = new QName(JBossSAMLURIConstants.X500_NSURI.get(), JBossSAMLConstants.ENCODING.get(),
-      JBossSAMLURIConstants.X500_PREFIX.get());
-
 
     private SAMLRequestedAttributeParser() {
-        super(JBossSAMLConstants.REQUESTED_ATTRIBUTE);
+        super(SAMLMetadataQNames.REQUESTED_ATTRIBUTE);
     }
 
     public static SAMLRequestedAttributeParser getInstance() {
@@ -50,24 +44,22 @@ public class SAMLRequestedAttributeParser extends AbstractStaxSamlParser<Request
 
     @Override
     protected RequestedAttributeType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
-        RequestedAttributeType attributeType;
+        RequestedAttributeType attributeType = new RequestedAttributeType(StaxParserUtil.getRequiredAttributeValue(element, SAMLAssertionQNames.ATTR_NAME));
 
-        // TODO: replace all constants with SamlMetadataQNames ones
-        attributeType = new RequestedAttributeType(StaxParserUtil.getRequiredAttributeValue(element, SAMLAssertionQNames.ATTR_NAME));
-        attributeType.setFriendlyName(StaxParserUtil.getAttributeValue(element, JBossSAMLConstants.FRIENDLY_NAME.get()));
-        attributeType.setIsRequired(StaxParserUtil.getBooleanAttributeValue(element, JBossSAMLConstants.IS_REQUIRED.get()));
-        attributeType.setNameFormat(StaxParserUtil.getAttributeValue(element, JBossSAMLConstants.NAME_FORMAT.get()));
+        attributeType.setFriendlyName(StaxParserUtil.getAttributeValue(element, SAMLMetadataQNames.ATTR_FRIENDLY_NAME));
+        attributeType.setIsRequired(StaxParserUtil.getBooleanAttributeValue(element, SAMLMetadataQNames.ATTR_IS_REQUIRED));
+        attributeType.setNameFormat(StaxParserUtil.getAttributeValue(element, SAMLMetadataQNames.ATTR_NAME_FORMAT));
 
-        Attribute x500EncodingAttr = element.getAttributeByName(X500_ENCODING);
-        if (x500EncodingAttr != null) {
-            attributeType.getOtherAttributes().put(x500EncodingAttr.getName(), StaxParserUtil.getAttributeValue(x500EncodingAttr));
+        String encoding = StaxParserUtil.getAttributeValue(element, SAMLMetadataQNames.ATTR_X500_ENCODING);
+        if (encoding != null && !encoding.isEmpty()) {
+            attributeType.getOtherAttributes().put(SAMLMetadataQNames.ATTR_X500_ENCODING.getQName(), encoding);
         }
 
         return attributeType;
     }
 
     @Override
-    protected void processSubElement(XMLEventReader xmlEventReader, RequestedAttributeType target, JBossSAMLConstants element, StartElement elementDetail) throws ParsingException {
+    protected void processSubElement(XMLEventReader xmlEventReader, RequestedAttributeType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
         switch (element) {
             case ATTRIBUTE_VALUE:
                 target.addAttributeValue(SAMLAttributeValueParser.getInstance().parse(xmlEventReader));
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLRoleDecriptorTypeParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLRoleDecriptorTypeParser.java
new file mode 100644
index 0000000..bf7cfc8
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLRoleDecriptorTypeParser.java
@@ -0,0 +1,54 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.metadata.RoleDescriptorType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+import org.w3c.dom.Element;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+
+/**
+ * @author mhajas
+ */
+public abstract class SAMLRoleDecriptorTypeParser<T extends RoleDescriptorType> extends AbstractStaxSamlMetadataParser<T> {
+
+    public SAMLRoleDecriptorTypeParser(SAMLMetadataQNames expectedStartElement) {
+        super(expectedStartElement);
+    }
+
+    protected void parseOptionalArguments(StartElement element, RoleDescriptorType descriptor) throws ParsingException {
+        descriptor.setID(StaxParserUtil.getAttributeValue(element, SAMLMetadataQNames.ATTR_ID));
+        descriptor.setValidUntil(StaxParserUtil.getXmlTimeAttributeValue(element, SAMLMetadataQNames.ATTR_VALID_UNTIL));
+        descriptor.setCacheDuration(StaxParserUtil.getXmlDurationAttributeValue(element, SAMLMetadataQNames.ATTR_CACHE_DURATION));
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, T target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch (element) {
+            case KEY_DESCRIPTOR:
+                target.addKeyDescriptor(SAMLKeyDescriptorParser.getInstance().parse(xmlEventReader));
+                break;
+
+            case SIGNATURE:
+                Element sig = StaxParserUtil.getDOMElement(xmlEventReader);
+                target.setSignature(sig);
+                break;
+
+            case EXTENSIONS:
+                target.setExtensions(SAMLExtensionsParser.getInstance().parse(xmlEventReader));
+                break;
+
+            case ORGANIZATION:
+                target.setOrganization(SAMLOrganizationParser.getInstance().parse(xmlEventReader));
+                break;
+
+            case CONTACT_PERSON:
+                target.addContactPerson(SAMLContactPersonParser.getInstance().parse(xmlEventReader));
+                break;
+
+            default:
+                throw LOGGER.parserUnknownTag(StaxParserUtil.getElementName(elementDetail), elementDetail.getLocation());
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLSingleLogoutServiceParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLSingleLogoutServiceParser.java
new file mode 100644
index 0000000..7d7abd6
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLSingleLogoutServiceParser.java
@@ -0,0 +1,17 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+/**
+ * @author mhajas
+ */
+public class SAMLSingleLogoutServiceParser extends SAMLEndpointTypeParser {
+
+    private static final SAMLSingleLogoutServiceParser INSTANCE = new SAMLSingleLogoutServiceParser();
+
+    public SAMLSingleLogoutServiceParser() {
+        super(SAMLMetadataQNames.SINGLE_LOGOUT_SERVICE);
+    }
+
+    public static SAMLSingleLogoutServiceParser getInstance() {
+        return INSTANCE;
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLSingleSignOnServiceParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLSingleSignOnServiceParser.java
new file mode 100644
index 0000000..b713ae3
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLSingleSignOnServiceParser.java
@@ -0,0 +1,17 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+/**
+ * @author mhajas
+ */
+public class SAMLSingleSignOnServiceParser extends SAMLEndpointTypeParser {
+
+    private static final SAMLSingleSignOnServiceParser INSTANCE = new SAMLSingleSignOnServiceParser();
+
+    public SAMLSingleSignOnServiceParser() {
+        super(SAMLMetadataQNames.SINGLE_SIGNON_SERVICE);
+    }
+
+    public static SAMLSingleSignOnServiceParser getInstance() {
+        return INSTANCE;
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLSPSSODescriptorParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLSPSSODescriptorParser.java
new file mode 100644
index 0000000..84eeb1a
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLSPSSODescriptorParser.java
@@ -0,0 +1,65 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+import java.util.List;
+
+import static org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLMetadataQNames.SP_SSO_DESCRIPTOR;
+
+/**
+ * @author mhajas
+ */
+public class SAMLSPSSODescriptorParser extends SAMLSSODescriptorTypeParser<SPSSODescriptorType> {
+
+    private static final SAMLSPSSODescriptorParser INSTANCE = new SAMLSPSSODescriptorParser();
+
+    private SAMLSPSSODescriptorParser() {
+        super(SP_SSO_DESCRIPTOR);
+    }
+
+    public static SAMLSPSSODescriptorParser getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    protected SPSSODescriptorType instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
+        List<String> protocolEnum = StaxParserUtil.getRequiredStringListAttributeValue(element, SAMLMetadataQNames.ATTR_PROTOCOL_SUPPORT_ENUMERATION);
+        SPSSODescriptorType descriptor = new SPSSODescriptorType(protocolEnum);
+
+        // Role descriptor optional arguments
+        parseOptionalArguments(element, descriptor);
+
+        // SPSSODecsriptor optional attributes
+        Boolean authnRequestsSigned = StaxParserUtil.getBooleanAttributeValue(element, SAMLMetadataQNames.ATTR_AUTHN_REQUESTS_SIGNED);
+        if (authnRequestsSigned != null) {
+            descriptor.setAuthnRequestsSigned(authnRequestsSigned);
+        }
+
+        Boolean wantAssertionSigned = StaxParserUtil.getBooleanAttributeValue(element, SAMLMetadataQNames.ATTR_WANT_ASSERTIONS_SIGNED);
+        if (wantAssertionSigned != null) {
+            descriptor.setWantAssertionsSigned(wantAssertionSigned);
+        }
+
+        return descriptor;
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, SPSSODescriptorType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch (element) {
+            case ASSERTION_CONSUMER_SERVICE:
+                target.addAssertionConsumerService(SAMLAssertionConsumerServiceParser.getInstance().parse(xmlEventReader));
+                break;
+
+            case ATTRIBUTE_CONSUMING_SERVICE:
+                target.addAttributeConsumerService(SAMLAttributeConsumingServiceParser.getInstance().parse(xmlEventReader));
+                break;
+
+            default:
+                super.processSubElement(xmlEventReader, target, element, elementDetail);
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLSSODescriptorTypeParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLSSODescriptorTypeParser.java
new file mode 100644
index 0000000..65ca097
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLSSODescriptorTypeParser.java
@@ -0,0 +1,43 @@
+package org.keycloak.saml.processing.core.parsers.saml.metadata;
+
+import org.keycloak.dom.saml.v2.metadata.SSODescriptorType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+
+/**
+ * @author mhajas
+ */
+public abstract class SAMLSSODescriptorTypeParser<T extends SSODescriptorType> extends SAMLRoleDecriptorTypeParser<T> {
+
+    public SAMLSSODescriptorTypeParser(SAMLMetadataQNames expectedStartElement) {
+        super(expectedStartElement);
+    }
+
+    @Override
+    protected void processSubElement(XMLEventReader xmlEventReader, T target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
+        switch (element) {
+            case ARTIFACT_RESOLUTION_SERVICE:
+                target.addArtifactResolutionService(SAMLArtifactResolutionServiceParser.getInstance().parse(xmlEventReader));
+                break;
+
+            case SINGLE_LOGOUT_SERVICE:
+                target.addSingleLogoutService(SAMLSingleLogoutServiceParser.getInstance().parse(xmlEventReader));
+                break;
+
+            case MANAGE_NAMEID_SERVICE:
+                target.addSingleLogoutService(SAMLManageNameIDServiceParser.getInstance().parse(xmlEventReader));
+                break;
+
+            case NAMEID_FORMAT:
+                StaxParserUtil.advance(xmlEventReader);
+                target.addNameIDFormat(StaxParserUtil.getElementText(xmlEventReader));
+                break;
+
+            default:
+                super.processSubElement(xmlEventReader, target, element, elementDetail);
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParser.java
index b469547..52599a1 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParser.java
@@ -16,6 +16,7 @@
  */
 package org.keycloak.saml.processing.core.parsers.saml;
 
+import org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLMetadataQNames;
 import org.keycloak.saml.processing.core.parsers.saml.protocol.SAMLAttributeQueryParser;
 import org.keycloak.saml.processing.core.parsers.saml.protocol.SAMLSloRequestParser;
 import org.keycloak.saml.processing.core.parsers.saml.protocol.SAMLSloResponseParser;
@@ -53,8 +54,6 @@ import java.io.InputStream;
  */
 public class SAMLParser extends AbstractParser {
 
-    private static final SAMLEntityDescriptorParser SAML_ENTITY_DESCRIPTOR_PARSER = new SAMLEntityDescriptorParser();
-    private static final SAMLEntitiesDescriptorParser SAML_ENTITIES_DESCRIPTOR_PARSER = new SAMLEntitiesDescriptorParser();
     private static final SAML11ResponseParser SAML_11_RESPONSE_PARSER = new SAML11ResponseParser();
     private static final SAML11RequestParser SAML_11_REQUEST_PARSER = new SAML11RequestParser();
 
@@ -88,9 +87,8 @@ public class SAMLParser extends AbstractParser {
 
         PARSERS.put(SAMLAssertionQNames.AUTHN_STATEMENT.getQName(),   new ParserFactory() { @Override public StaxParser create() { return SAMLAuthnStatementParser.getInstance(); }});
 
-        // TODO: Change to SamlMetadataElements
-        PARSERS.put(JBossSAMLConstants.ENTITY_DESCRIPTOR.getAsQName(),  new ParserFactory() { @Override public StaxParser create() { return SAML_ENTITY_DESCRIPTOR_PARSER; }});
-        PARSERS.put(JBossSAMLConstants.ENTITIES_DESCRIPTOR.getAsQName(),new ParserFactory() { @Override public StaxParser create() { return SAML_ENTITIES_DESCRIPTOR_PARSER; }});
+        PARSERS.put(SAMLMetadataQNames.ENTITY_DESCRIPTOR.getQName(),  new ParserFactory() { @Override public StaxParser create() { return SAMLEntityDescriptorParser.getInstance(); }});
+        PARSERS.put(SAMLMetadataQNames.ENTITIES_DESCRIPTOR.getQName(),new ParserFactory() { @Override public StaxParser create() { return SAMLEntitiesDescriptorParser.getInstance(); }});
 
         PARSERS.put(SAMLProtocolQNames.ATTRIBUTE_QUERY.getQName(),    new ParserFactory() { @Override public StaxParser create() { return SAMLAttributeQueryParser.getInstance(); }});
     }
diff --git a/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
index 9ab24be..c395f86 100644
--- a/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
+++ b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
@@ -16,24 +16,12 @@
  */
 package org.keycloak.saml.processing.core.parsers.saml;
 
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.security.PrivateKey;
-
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
-
 import org.keycloak.common.util.Base64;
 import org.keycloak.common.util.DerUtils;
 import org.keycloak.common.util.StreamUtil;
@@ -50,7 +38,21 @@ import org.keycloak.dom.saml.v2.assertion.NameIDType;
 import org.keycloak.dom.saml.v2.assertion.StatementAbstractType;
 import org.keycloak.dom.saml.v2.assertion.SubjectConfirmationType;
 import org.keycloak.dom.saml.v2.assertion.SubjectType;
+import org.keycloak.dom.saml.v2.metadata.AttributeAuthorityDescriptorType;
+import org.keycloak.dom.saml.v2.metadata.AttributeConsumingServiceType;
+import org.keycloak.dom.saml.v2.metadata.AuthnAuthorityDescriptorType;
+import org.keycloak.dom.saml.v2.metadata.EndpointType;
+import org.keycloak.dom.saml.v2.metadata.EntitiesDescriptorType;
 import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
+import org.keycloak.dom.saml.v2.metadata.IDPSSODescriptorType;
+import org.keycloak.dom.saml.v2.metadata.IndexedEndpointType;
+import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.dom.saml.v2.metadata.LocalizedNameType;
+import org.keycloak.dom.saml.v2.metadata.LocalizedURIType;
+import org.keycloak.dom.saml.v2.metadata.PDPDescriptorType;
+import org.keycloak.dom.saml.v2.metadata.RequestedAttributeType;
+import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
 import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
 import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
 import org.keycloak.dom.saml.v2.protocol.ResponseType;
@@ -60,6 +62,7 @@ import org.keycloak.dom.xmlsec.w3.xmldsig.KeyInfoType;
 import org.keycloak.dom.xmlsec.w3.xmldsig.RSAKeyValueType;
 import org.keycloak.dom.xmlsec.w3.xmldsig.X509CertificateType;
 import org.keycloak.dom.xmlsec.w3.xmldsig.X509DataType;
+import org.keycloak.dom.xmlsec.w3.xmlenc.EncryptionMethodType;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.common.exceptions.ConfigurationException;
 import org.keycloak.saml.common.exceptions.ParsingException;
@@ -67,10 +70,37 @@ import org.keycloak.saml.common.exceptions.ProcessingException;
 import org.keycloak.saml.processing.api.saml.v2.response.SAML2Response;
 import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
 import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
+import org.w3c.dom.Element;
+
+import javax.xml.namespace.QName;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
 import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+import java.util.Collections;
 import java.util.List;
-import org.hamcrest.Matcher;
-import org.w3c.dom.Element;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.emptyCollectionOf;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.everyItem;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 /**
  * Test class for SAML parser.
@@ -251,12 +281,341 @@ public class SAMLParserTest {
 
     @Test
     public void testSaml20MetadataEntityDescriptorIdP() throws Exception {
-        assertParsed("saml20-entity-descriptor-idp.xml", EntityDescriptorType.class);
+        EntityDescriptorType entityDescriptor = assertParsed("saml20-entity-descriptor-idp.xml", EntityDescriptorType.class);
+
+        List<EntityDescriptorType.EDTChoiceType> descriptors = entityDescriptor.getChoiceType();
+        assertThat(descriptors, hasSize(2));
+
+        // IDPSSO descriptor
+        IDPSSODescriptorType idpDescriptor = descriptors.get(0).getDescriptors().get(0).getIdpDescriptor();
+        assertThat(idpDescriptor, is(notNullValue()));
+        assertThat(idpDescriptor.isWantAuthnRequestsSigned(), is(true));
+        assertThat(idpDescriptor.getProtocolSupportEnumeration(), contains("urn:oasis:names:tc:SAML:2.0:protocol"));
+
+        // Key descriptor
+        List<KeyDescriptorType> keyDescriptors = idpDescriptor.getKeyDescriptor();
+        assertThat(keyDescriptors, hasSize(1));
+
+        KeyDescriptorType signingKey = keyDescriptors.get(0);
+        assertThat(signingKey.getUse(), is(KeyTypes.SIGNING));
+        assertThat(signingKey.getEncryptionMethod(), is(emptyCollectionOf(EncryptionMethodType.class)));
+        assertThat(signingKey.getKeyInfo().getElementsByTagName("ds:KeyName").item(0).getTextContent(), is("IdentityProvider.com SSO Key"));
+
+        // Single logout services
+        assertThat(idpDescriptor.getSingleLogoutService(), hasSize(2));
+        EndpointType singleLS1 = idpDescriptor.getSingleLogoutService().get(0);
+        assertThat(singleLS1.getBinding(), is(URI.create("urn:oasis:names:tc:SAML:2.0:bindings:SOAP")));
+        assertThat(singleLS1.getLocation(), is(URI.create("https://IdentityProvider.com/SAML/SLO/SOAP")));
+        assertThat(singleLS1.getResponseLocation(), is(nullValue()));
+        assertThat(singleLS1.getAny(), is(emptyCollectionOf(Object.class)));
+        assertThat(singleLS1.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+
+        EndpointType singleLS2 = idpDescriptor.getSingleLogoutService().get(1);
+        assertThat(singleLS2.getBinding(), is(URI.create("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")));
+        assertThat(singleLS2.getLocation(), is(URI.create("https://IdentityProvider.com/SAML/SLO/Browser")));
+        assertThat(singleLS2.getResponseLocation(), is(URI.create("https://IdentityProvider.com/SAML/SLO/Response")));
+        assertThat(singleLS2.getAny(), is(emptyCollectionOf(Object.class)));
+        assertThat(singleLS2.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+
+        // NameID
+        assertThat(idpDescriptor.getNameIDFormat(),
+                containsInAnyOrder("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName",
+                        "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
+                        "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
+                ));
+
+        // Single sign on services
+        assertThat(idpDescriptor.getSingleSignOnService(), hasSize(2));
+
+        EndpointType singleSO1 = idpDescriptor.getSingleSignOnService().get(0);
+        assertThat(singleSO1.getBinding(), is(URI.create("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")));
+        assertThat(singleSO1.getLocation(), is(URI.create("https://IdentityProvider.com/SAML/SSO/Browser")));
+        assertThat(singleSO1.getResponseLocation(), is(nullValue()));
+        assertThat(singleSO1.getAny(), is(emptyCollectionOf(Object.class)));
+        assertThat(singleSO1.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+
+        EndpointType singleSO2 = idpDescriptor.getSingleSignOnService().get(1);
+        assertThat(singleSO2.getBinding(), is(URI.create("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")));
+        assertThat(singleSO2.getLocation(), is(URI.create("https://IdentityProvider.com/SAML/SSO/Browser")));
+        assertThat(singleSO2.getResponseLocation(), is(nullValue()));
+        assertThat(singleSO2.getAny(), is(emptyCollectionOf(Object.class)));
+        assertThat(singleSO2.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+
+        // Attributes
+        assertThat(idpDescriptor.getAttribute(), hasSize(2));
+
+        AttributeType attr1 = idpDescriptor.getAttribute().get(0);
+        assertThat(attr1.getNameFormat(), is("urn:oasis:names:tc:SAML:2.0:attrname-format:uri"));
+        assertThat(attr1.getName(), is("urn:oid:1.3.6.1.4.1.5923.1.1.1.6"));
+        assertThat(attr1.getFriendlyName(), is("eduPersonPrincipalName"));
+        assertThat(attr1.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+        assertThat(attr1.getAttributeValue(), is(emptyCollectionOf(Object.class)));
+
+        AttributeType attr2 = idpDescriptor.getAttribute().get(1);
+        assertThat(attr2.getNameFormat(), is("urn:oasis:names:tc:SAML:2.0:attrname-format:uri"));
+        assertThat(attr2.getName(), is("urn:oid:1.3.6.1.4.1.5923.1.1.1.1"));
+        assertThat(attr2.getFriendlyName(), is("eduPersonAffiliation"));
+        assertThat(attr2.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+        assertThat(attr2.getAttributeValue(), containsInAnyOrder((Object) "member", "student", "faculty", "employee", "staff"));
+
+        // Organization
+        assertThat(entityDescriptor.getOrganization().getOrganizationName(), hasSize(1));
+        LocalizedNameType orgName = entityDescriptor.getOrganization().getOrganizationName().get(0);
+        assertThat(orgName.getLang(), is("en"));
+        assertThat(orgName.getValue(), is("Identity Providers R\n            US"));
+
+        assertThat(entityDescriptor.getOrganization().getOrganizationDisplayName(), hasSize(1));
+        LocalizedNameType orgDispName = entityDescriptor.getOrganization().getOrganizationDisplayName().get(0);
+        assertThat(orgDispName.getLang(), is("en"));
+        assertThat(orgDispName.getValue(), is("Identity Providers R US, a Division of Lerxst Corp."));
+
+        assertThat(entityDescriptor.getOrganization().getOrganizationURL(), hasSize(1));
+        LocalizedURIType orgURL = entityDescriptor.getOrganization().getOrganizationURL().get(0);
+        assertThat(orgURL.getLang(), is("en"));
+        assertThat(orgURL.getValue(), is(URI.create("https://IdentityProvider.com")));
+    }
+
+    @Test
+    public void testSAML20MetadataEntityDescriptorAttrA() throws Exception{
+        EntityDescriptorType entityDescriptor = assertParsed("saml20-entity-descriptor-idp.xml", EntityDescriptorType.class);
+
+        List<EntityDescriptorType.EDTChoiceType> descriptors = entityDescriptor.getChoiceType();
+        assertThat(descriptors, hasSize(2));
+
+        AttributeAuthorityDescriptorType aaDescriptor = descriptors.get(1).getDescriptors().get(0).getAttribDescriptor();
+        assertThat(aaDescriptor, is(notNullValue()));
+        assertThat(aaDescriptor.getProtocolSupportEnumeration(), contains("urn:oasis:names:tc:SAML:2.0:protocol"));
+
+        // Key descriptor
+        List<KeyDescriptorType> keyDescriptors = aaDescriptor.getKeyDescriptor();
+        assertThat(keyDescriptors, hasSize(1));
+
+        KeyDescriptorType signingKey = keyDescriptors.get(0);
+        assertThat(signingKey.getUse(), is(KeyTypes.SIGNING));
+        assertThat(signingKey.getEncryptionMethod(), is(emptyCollectionOf(EncryptionMethodType.class)));
+        assertThat(signingKey.getKeyInfo().getElementsByTagName("ds:KeyName").item(0).getTextContent(), is("IdentityProvider.com AA Key"));
+
+        // Attribute service
+        assertThat(aaDescriptor.getAttributeService(), hasSize(1));
+        EndpointType attrServ = aaDescriptor.getAttributeService().get(0);
+        assertThat(attrServ.getBinding(), is(URI.create("urn:oasis:names:tc:SAML:2.0:bindings:SOAP")));
+        assertThat(attrServ.getLocation(), is(URI.create("https://IdentityProvider.com/SAML/AA/SOAP")));
+        assertThat(attrServ.getResponseLocation(), is(nullValue()));
+        assertThat(attrServ.getAny(), is(emptyCollectionOf(Object.class)));
+        assertThat(attrServ.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+
+        // AssertionIDRequestService
+        assertThat(aaDescriptor.getAssertionIDRequestService(), hasSize(1));
+        EndpointType assertIDRServ = aaDescriptor.getAssertionIDRequestService().get(0);
+        assertThat(assertIDRServ.getBinding(), is(URI.create("urn:oasis:names:tc:SAML:2.0:bindings:URI")));
+        assertThat(assertIDRServ.getLocation(), is(URI.create("https://IdentityProvider.com/SAML/AA/URI")));
+        assertThat(assertIDRServ.getResponseLocation(), is(nullValue()));
+        assertThat(assertIDRServ.getAny(), is(emptyCollectionOf(Object.class)));
+        assertThat(assertIDRServ.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+
+        // NameID
+        assertThat(aaDescriptor.getNameIDFormat(),
+                containsInAnyOrder("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName",
+                        "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
+                        "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
+                ));
+
+        assertThat(aaDescriptor.getAttribute(), hasSize(2));
+
+        AttributeType attr1 = aaDescriptor.getAttribute().get(0);
+        assertThat(attr1.getNameFormat(), is("urn:oasis:names:tc:SAML:2.0:attrname-format:uri"));
+        assertThat(attr1.getName(), is("urn:oid:1.3.6.1.4.1.5923.1.1.1.6"));
+        assertThat(attr1.getFriendlyName(), is("eduPersonPrincipalName"));
+        assertThat(attr1.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+        assertThat(attr1.getAttributeValue(), is(emptyCollectionOf(Object.class)));
+
+        AttributeType attr2 = aaDescriptor.getAttribute().get(1);
+        assertThat(attr2.getNameFormat(), is("urn:oasis:names:tc:SAML:2.0:attrname-format:uri"));
+        assertThat(attr2.getName(), is("urn:oid:1.3.6.1.4.1.5923.1.1.1.1"));
+        assertThat(attr2.getFriendlyName(), is("eduPersonAffiliation"));
+        assertThat(attr2.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+        assertThat(attr2.getAttributeValue(), containsInAnyOrder((Object) "member", "student", "faculty", "employee", "staff"));
     }
 
     @Test
     public void testSaml20MetadataEntityDescriptorSP() throws Exception {
-        assertParsed("saml20-entity-descriptor-sp.xml", EntityDescriptorType.class);
+        EntityDescriptorType entityDescriptor = assertParsed("saml20-entity-descriptor-sp.xml", EntityDescriptorType.class);
+
+        assertThat(entityDescriptor.getEntityID(), is("https://ServiceProvider.com/SAML"));
+        assertThat(entityDescriptor.getValidUntil(), is(nullValue()));
+        assertThat(entityDescriptor.getCacheDuration(), is(nullValue()));
+        assertThat(entityDescriptor.getID(), is(nullValue()));
+
+        assertThat(entityDescriptor.getExtensions(), is(nullValue()));
+
+        List<EntityDescriptorType.EDTChoiceType> descriptors = entityDescriptor.getChoiceType();
+        assertThat(descriptors, hasSize(1));
+
+        // SP Descriptor
+        SPSSODescriptorType spDescriptor = descriptors.get(0).getDescriptors().get(0).getSpDescriptor();
+        assertThat(spDescriptor, is(notNullValue()));
+
+        assertThat(spDescriptor.isAuthnRequestsSigned(), is(true));
+        assertThat(spDescriptor.isWantAssertionsSigned(), is(false));
+        assertThat(spDescriptor.getProtocolSupportEnumeration(), contains("urn:oasis:names:tc:SAML:2.0:protocol"));
+
+        // Key descriptor
+        List<KeyDescriptorType> keyDescriptors = spDescriptor.getKeyDescriptor();
+        assertThat(keyDescriptors, hasSize(2));
+
+        KeyDescriptorType signingKey = keyDescriptors.get(0);
+        assertThat(signingKey.getUse(), is(KeyTypes.SIGNING));
+        assertThat(signingKey.getEncryptionMethod(), is(emptyCollectionOf(EncryptionMethodType.class)));
+        assertThat(signingKey.getKeyInfo().getElementsByTagName("ds:KeyName").item(0).getTextContent(), is("ServiceProvider.com SSO Key"));
+
+        KeyDescriptorType encryptionKey = keyDescriptors.get(1);
+        assertThat(encryptionKey.getUse(), is(KeyTypes.ENCRYPTION));
+        assertThat(encryptionKey.getKeyInfo().getElementsByTagName("ds:KeyName").item(0).getTextContent(), is("ServiceProvider.com Encrypt Key"));
+
+        List<EncryptionMethodType> encryptionMethods = encryptionKey.getEncryptionMethod();
+        assertThat(encryptionMethods, Matchers.<EncryptionMethodType>hasSize(1));
+        assertThat(encryptionMethods.get(0).getAlgorithm(), is("http://www.w3.org/2001/04/xmlenc#rsa-1_5"));
+        assertThat(encryptionMethods.get(0).getEncryptionMethod(), is(nullValue()));
+
+        // Single logout services
+        assertThat(spDescriptor.getSingleLogoutService(), hasSize(2));
+        EndpointType singleLS1 = spDescriptor.getSingleLogoutService().get(0);
+        assertThat(singleLS1.getBinding(), is(URI.create("urn:oasis:names:tc:SAML:2.0:bindings:SOAP")));
+        assertThat(singleLS1.getLocation(), is(URI.create("https://ServiceProvider.com/SAML/SLO/SOAP")));
+        assertThat(singleLS1.getResponseLocation(), is(nullValue()));
+        assertThat(singleLS1.getAny(), is(emptyCollectionOf(Object.class)));
+        assertThat(singleLS1.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+
+        EndpointType singleLS2 = spDescriptor.getSingleLogoutService().get(1);
+        assertThat(singleLS2.getBinding(), is(URI.create("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")));
+        assertThat(singleLS2.getLocation(), is(URI.create("https://ServiceProvider.com/SAML/SLO/Browser")));
+        assertThat(singleLS2.getResponseLocation(), is(URI.create("https://ServiceProvider.com/SAML/SLO/Response")));
+        assertThat(singleLS2.getAny(), is(emptyCollectionOf(Object.class)));
+        assertThat(singleLS2.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+
+        // NameID
+        assertThat(spDescriptor.getNameIDFormat(), contains("urn:oasis:names:tc:SAML:2.0:nameid-format:transient"));
+
+        // Assertion consumer services
+        List<IndexedEndpointType> assertionConsumerServices = spDescriptor.getAssertionConsumerService();
+        assertThat(assertionConsumerServices, hasSize(2));
+
+        IndexedEndpointType assertionCS1 = assertionConsumerServices.get(0);
+        assertThat(assertionCS1.getIndex(), is(0));
+        assertThat(assertionCS1.isIsDefault(), is(true));
+        assertThat(assertionCS1.getBinding(), is(URI.create("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact")));
+        assertThat(assertionCS1.getLocation(), is(URI.create("https://ServiceProvider.com/SAML/SSO/Artifact")));
+        assertThat(assertionCS1.getResponseLocation(), is(nullValue()));
+        assertThat(assertionCS1.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+
+        IndexedEndpointType assertionCS2 = assertionConsumerServices.get(1);
+        assertThat(assertionCS2.getIndex(), is(1));
+        assertThat(assertionCS2.isIsDefault(), is(nullValue()));
+        assertThat(assertionCS2.getBinding(), is(URI.create("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")));
+        assertThat(assertionCS2.getLocation(), is(URI.create("https://ServiceProvider.com/SAML/SSO/POST")));
+        assertThat(assertionCS2.getResponseLocation(), is(nullValue()));
+        assertThat(assertionCS2.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+
+        // Attribute consuming services
+        List<AttributeConsumingServiceType> attributeConsumingServices = spDescriptor.getAttributeConsumingService();
+        assertThat(attributeConsumingServices, hasSize(1));
+
+        AttributeConsumingServiceType attributeConsumingService = attributeConsumingServices.get(0);
+        assertThat(attributeConsumingService.getIndex(), is(0));
+        assertThat(attributeConsumingService.getServiceName(), hasSize(1));
+        LocalizedNameType servName = attributeConsumingService.getServiceName().get(0);
+        assertThat(servName.getLang(), is("en"));
+        assertThat(servName.getValue(), is("Academic Journals R US"));
+        assertThat(attributeConsumingService.getServiceDescription(), is(emptyCollectionOf(LocalizedNameType.class)));
+
+        List<RequestedAttributeType> requestedAttributes = attributeConsumingService.getRequestedAttribute();
+        assertThat(requestedAttributes, hasSize(1));
+
+        // Requested attribute
+        RequestedAttributeType requestedAttribute = requestedAttributes.get(0);
+        assertThat(requestedAttribute.getNameFormat(), is("urn:oasis:names:tc:SAML:2.0:attrname-format:uri"));
+        assertThat(requestedAttribute.getName(), is("urn:oid:1.3.6.1.4.1.5923.1.1.1.7"));
+        assertThat(requestedAttribute.getFriendlyName(), is("eduPersonEntitlement"));
+
+        assertThat(requestedAttribute.getAttributeValue(), hasSize(1));
+        assertThat((String) requestedAttribute.getAttributeValue().get(0), is("https://ServiceProvider.com/entitlements/123456789"));
+
+        assertThat(requestedAttribute.getOtherAttributes(), is(Collections.<QName, String>emptyMap()));
+
+        // Organization
+        assertThat(entityDescriptor.getOrganization().getOrganizationName(), hasSize(1));
+        LocalizedNameType orgName = entityDescriptor.getOrganization().getOrganizationName().get(0);
+        assertThat(orgName.getLang(), is("en"));
+        assertThat(orgName.getValue(), is("Academic Journals R\n            US"));
+
+        assertThat(entityDescriptor.getOrganization().getOrganizationDisplayName(), hasSize(1));
+        LocalizedNameType orgDispName = entityDescriptor.getOrganization().getOrganizationDisplayName().get(0);
+        assertThat(orgDispName.getLang(), is("en"));
+        assertThat(orgDispName.getValue(), is("Academic Journals R US, a Division of Dirk Corp."));
+
+        assertThat(entityDescriptor.getOrganization().getOrganizationURL(), hasSize(1));
+        LocalizedURIType orgURL = entityDescriptor.getOrganization().getOrganizationURL().get(0);
+        assertThat(orgURL.getLang(), is("en"));
+        assertThat(orgURL.getValue(), is(URI.create("https://ServiceProvider.com")));
+    }
+
+    @Test
+    public void testSaml20MetadataEntityDescriptorPDP() throws Exception {
+        EntityDescriptorType descriptor = assertParsed("saml20-entity-descriptor-pdp.xml", EntityDescriptorType.class);
+
+        assertThat(descriptor.getChoiceType(), Matchers.<EntityDescriptorType.EDTChoiceType>hasSize(1));
+        assertThat(descriptor.getChoiceType().get(0).getDescriptors().get(0).getPdpDescriptor(), is(notNullValue()));
+
+        PDPDescriptorType pdpDescriptor = descriptor.getChoiceType().get(0).getDescriptors().get(0).getPdpDescriptor();
+
+        assertThat(pdpDescriptor.getKeyDescriptor(), Matchers.<KeyDescriptorType>hasSize(1));
+
+        KeyDescriptorType keyDescriptorType = pdpDescriptor.getKeyDescriptor().get(0);
+        assertThat(keyDescriptorType.getEncryptionMethod(), Matchers.<EncryptionMethodType>hasSize(1));
+
+        EncryptionMethodType encryptionMethodType = keyDescriptorType.getEncryptionMethod().get(0);
+        assertThat(encryptionMethodType.getAlgorithm(), is("http://www.example.com/"));
+
+        EncryptionMethodType.EncryptionMethod encryptionMethod = encryptionMethodType.getEncryptionMethod();
+        assertThat(encryptionMethod.getKeySize(), is(BigInteger.ONE));
+        assertThat(encryptionMethod.getOAEPparams(), is("GpM7".getBytes()));
+
+        // EndpointType parser already tested so we are not checking further
+        assertThat(pdpDescriptor.getAuthzService(), Matchers.<EndpointType>hasSize(1));
+        assertThat(pdpDescriptor.getAssertionIDRequestService(), Matchers.<EndpointType>hasSize(1));
+    }
+
+    @Test
+    public void testSaml20MetadataEntityDescriptorAuthnAuthority() throws Exception {
+        EntityDescriptorType descriptor = assertParsed("saml20-entity-descriptor-authn-authority.xml", EntityDescriptorType.class);
+
+        assertThat(descriptor.getChoiceType(), Matchers.<EntityDescriptorType.EDTChoiceType>hasSize(1));
+        assertThat(descriptor.getChoiceType().get(0).getDescriptors().get(0).getAuthnDescriptor(), is(notNullValue()));
+
+        AuthnAuthorityDescriptorType authnDescriptor = descriptor.getChoiceType().get(0).getDescriptors().get(0).getAuthnDescriptor();
+
+        assertThat(authnDescriptor.getAssertionIDRequestService(), hasSize(1));
+        assertThat(authnDescriptor.getAuthnQueryService(),  hasSize(1));
+        assertThat(authnDescriptor.getProtocolSupportEnumeration(), containsInAnyOrder("http://www.example.com/", "http://www.example2.com/"));
+    }
+
+    @Test
+    public void testSaml20MetadataEntitiesDescriptor() throws Exception {
+        EntitiesDescriptorType entities = assertParsed("saml20-entities-descriptor.xml", EntitiesDescriptorType.class);
+
+        assertThat(entities.getName(), is("https://your-federation.org/metadata/federation-name.xml"));
+        assertThat(entities.getID(), is(nullValue()));
+        assertThat(entities.getCacheDuration(), is(nullValue()));
+        assertThat(entities.getExtensions(), is(nullValue()));
+        assertThat(entities.getSignature(), is(nullValue()));
+        assertThat(entities.getValidUntil(), is(nullValue()));
+        assertThat(entities.getEntityDescriptor(), hasSize(3));
+        assertThat(entities.getEntityDescriptor().get(0), instanceOf(EntityDescriptorType.class));
+        assertThat(entities.getEntityDescriptor().get(1), instanceOf(EntityDescriptorType.class));
+        assertThat(entities.getEntityDescriptor().get(2), instanceOf(EntitiesDescriptorType.class));
+
+        EntitiesDescriptorType nestedEntities = (EntitiesDescriptorType) entities.getEntityDescriptor().get(2);
+        assertThat(nestedEntities.getEntityDescriptor(), hasSize(2));
     }
 
     @Test
@@ -342,6 +701,86 @@ public class SAMLParserTest {
     }
 
     @Test
+    public void testInvalidEndElement() throws Exception {
+        thrown.expect(ParsingException.class);
+        thrown.expectMessage(containsString("The element type \"NameIDFormat\" must be terminated by the matching end-tag \"</NameIDFormat>\"."));
+
+        assertParsed("saml20-entity-descriptor-idp-invalid-end-element.xml", EntityDescriptorType.class);
+    }
+
+    @Test
+    public void testMissingRequiredAttributeIDPSSODescriptorType()  throws Exception {
+        testMissingAttribute("IDPSSODescriptorType", "protocolSupportEnumeration");
+    }
+
+    @Test
+    public void testMissingRequiredAttributeSPSSODescriptorType()  throws Exception {
+        testMissingAttribute("SPSSODescriptorType", "protocolSupportEnumeration");
+    }
+
+    @Test
+    public void testMissingRequiredAttributeAttributeAuthorityDescriptorType()  throws Exception {
+        testMissingAttribute("AttributeAuthorityDescriptorType", "protocolSupportEnumeration");
+    }
+
+    @Test
+    public void testMissingRequiredAttributeAuthnAuthorityDescriptorType()  throws Exception {
+        testMissingAttribute("AuthnAuthorityDescriptorType", "protocolSupportEnumeration");
+    }
+
+    @Test
+    public void testMissingRequiredAttributePDPDescriptorType()  throws Exception {
+        testMissingAttribute("PDPDescriptorType", "protocolSupportEnumeration");
+    }
+
+    @Test
+    public void testMissingRequiredAttributeAttributeConsumingServiceType()  throws Exception {
+        testMissingAttribute("AttributeConsumingServiceType", "index");
+    }
+
+    @Test
+    public void testMissingRequiredAttributeAttributeType()  throws Exception {
+        testMissingAttribute("AttributeType", "Name");
+    }
+
+    @Test
+    public void testMissingRequiredAttributeContactType()  throws Exception {
+        testMissingAttribute("ContactType", "contactType");
+    }
+
+    @Test
+    public void testMissingRequiredAttributeEncryptionMethodType()  throws Exception {
+        testMissingAttribute("EncryptionMethodType", "Algorithm");
+    }
+
+    @Test
+    public void testMissingRequiredAttributeEndpointTypeBinding()  throws Exception {
+        testMissingAttribute("EndpointType", "Binding");
+    }
+
+    @Test
+    public void testMissingRequiredAttributeEndpointTypeLocation()  throws Exception {
+        testMissingAttribute("EndpointType", "Location");
+    }
+
+    @Test
+    public void testMissingRequiredAttributeEntityDescriptorType()  throws Exception {
+        testMissingAttribute("EntityDescriptorType", "entityID");
+    }
+
+    @Test
+    public void testMissingRequiredAttributeRequestedAttributeType()  throws Exception {
+        testMissingAttribute("RequestedAttributeType", "Name");
+    }
+
+    private void testMissingAttribute(String type, String attributeName) throws Exception {
+        thrown.expect(ParsingException.class);
+        thrown.expectMessage(containsString("Parser: Required attribute missing: " + attributeName));
+
+        assertParsed("missing-attribute/saml20-" + type + "-" + attributeName + ".xml", EntityDescriptorType.class);
+    }
+
+    @Test
     public void testAuthnRequestScoping() throws Exception {
         assertParsed("KEYCLOAK-6109-authnrequest-scoping.xml", AuthnRequestType.class);
     }
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-AttributeAuthorityDescriptorType-protocolSupportEnumeration.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-AttributeAuthorityDescriptorType-protocolSupportEnumeration.xml
new file mode 100644
index 0000000..069decd
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-AttributeAuthorityDescriptorType-protocolSupportEnumeration.xml
@@ -0,0 +1,19 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  entityID="https://IdentityProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+
+    <AttributeAuthorityDescriptor>
+
+    </AttributeAuthorityDescriptor>
+    <Organization>
+        <OrganizationName xml:lang="en">Identity Providers R
+            US</OrganizationName>
+        <OrganizationDisplayName xml:lang="en">
+            Identity Providers R US, a Division of Lerxst Corp.
+        </OrganizationDisplayName>
+        <OrganizationURL
+            xml:lang="en">https://IdentityProvider.com</OrganizationURL>
+    </Organization>
+</EntityDescriptor>
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-AttributeConsumingServiceType-index.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-AttributeConsumingServiceType-index.xml
new file mode 100644
index 0000000..af8afb3
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-AttributeConsumingServiceType-index.xml
@@ -0,0 +1,20 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  entityID="https://ServiceProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <SPSSODescriptor AuthnRequestsSigned="true"
+                     protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <AttributeConsumingService>
+            <ServiceName xml:lang="en">Academic Journals R US</ServiceName>
+            <RequestedAttribute
+                NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
+                Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.7"
+                FriendlyName="eduPersonEntitlement">
+                <saml:AttributeValue>
+                    https://ServiceProvider.com/entitlements/123456789
+                </saml:AttributeValue>
+            </RequestedAttribute>
+        </AttributeConsumingService>
+    </SPSSODescriptor>
+</EntityDescriptor>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-AttributeType-Name.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-AttributeType-Name.xml
new file mode 100644
index 0000000..efbe385
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-AttributeType-Name.xml
@@ -0,0 +1,13 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  entityID="https://IdentityProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <IDPSSODescriptor WantAuthnRequestsSigned="true"
+                      protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <saml:Attribute
+            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
+            FriendlyName="eduPersonPrincipalName">
+        </saml:Attribute>
+    </IDPSSODescriptor>
+</EntityDescriptor>
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-AuthnAuthorityDescriptorType-protocolSupportEnumeration.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-AuthnAuthorityDescriptorType-protocolSupportEnumeration.xml
new file mode 100644
index 0000000..301bc1e
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-AuthnAuthorityDescriptorType-protocolSupportEnumeration.xml
@@ -0,0 +1,10 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
+                  entityID="https://IdentityProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <AuthnAuthorityDescriptor ID="ID">
+
+    </AuthnAuthorityDescriptor>
+</EntityDescriptor>
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-ContactType-contactType.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-ContactType-contactType.xml
new file mode 100644
index 0000000..b4e12c1
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-ContactType-contactType.xml
@@ -0,0 +1,18 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
+                  entityID="https://IdentityProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <PDPDescriptor ID="ID" protocolSupportEnumeration="http://www.example.com/ http://www.example.com/">
+
+        <ContactPerson>
+            <Company>string</Company>
+            <GivenName>string</GivenName>
+            <SurName>string</SurName>
+            <EmailAddress>http://www.example.com/</EmailAddress>
+            <TelephoneNumber>string</TelephoneNumber>
+        </ContactPerson>
+
+    </PDPDescriptor>
+</EntityDescriptor>
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-EncryptionMethodType-Algorithm.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-EncryptionMethodType-Algorithm.xml
new file mode 100644
index 0000000..8ce2332
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-EncryptionMethodType-Algorithm.xml
@@ -0,0 +1,19 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
+                  entityID="https://IdentityProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <PDPDescriptor ID="ID" protocolSupportEnumeration="http://www.example.com/ http://www.example.com/">
+
+        <KeyDescriptor>
+            <ds:KeyInfo>
+                <ds:KeyName>string</ds:KeyName>
+            </ds:KeyInfo>
+            <EncryptionMethod>
+                <xenc:KeySize>1</xenc:KeySize>
+                <xenc:OAEPparams>GpM7</xenc:OAEPparams>
+            </EncryptionMethod>
+        </KeyDescriptor>
+    </PDPDescriptor>
+</EntityDescriptor>
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-EndpointType-Binding.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-EndpointType-Binding.xml
new file mode 100644
index 0000000..fe1d186
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-EndpointType-Binding.xml
@@ -0,0 +1,12 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  entityID="https://ServiceProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <SPSSODescriptor AuthnRequestsSigned="true"
+                     protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+
+        <SingleLogoutService
+            Location="https://ServiceProvider.com/SAML/SLO/SOAP"/>
+    </SPSSODescriptor>
+</EntityDescriptor>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-EndpointType-Location.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-EndpointType-Location.xml
new file mode 100644
index 0000000..8eb5a5b
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-EndpointType-Location.xml
@@ -0,0 +1,11 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  entityID="https://ServiceProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <SPSSODescriptor AuthnRequestsSigned="true"
+                     protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <SingleLogoutService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"/>
+    </SPSSODescriptor>
+</EntityDescriptor>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-EntityDescriptorType-entityID.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-EntityDescriptorType-entityID.xml
new file mode 100644
index 0000000..071803d
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-EntityDescriptorType-entityID.xml
@@ -0,0 +1,4 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+</EntityDescriptor>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-IDPSSODescriptorType-protocolSupportEnumeration.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-IDPSSODescriptorType-protocolSupportEnumeration.xml
new file mode 100644
index 0000000..5a971c9
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-IDPSSODescriptorType-protocolSupportEnumeration.xml
@@ -0,0 +1,9 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  entityID="https://IdentityProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <IDPSSODescriptor WantAuthnRequestsSigned="true">
+
+    </IDPSSODescriptor>
+</EntityDescriptor>
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-PDPDescriptorType-protocolSupportEnumeration.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-PDPDescriptorType-protocolSupportEnumeration.xml
new file mode 100644
index 0000000..c6b2a63
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-PDPDescriptorType-protocolSupportEnumeration.xml
@@ -0,0 +1,10 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
+                  entityID="https://IdentityProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <PDPDescriptor ID="ID">
+
+    </PDPDescriptor>
+</EntityDescriptor>
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-RequestedAttributeType-Name.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-RequestedAttributeType-Name.xml
new file mode 100644
index 0000000..7695618
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-RequestedAttributeType-Name.xml
@@ -0,0 +1,19 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  entityID="https://ServiceProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <SPSSODescriptor AuthnRequestsSigned="true"
+                     protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <AttributeConsumingService index="0">
+            <ServiceName xml:lang="en">Academic Journals R US</ServiceName>
+            <RequestedAttribute
+                NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
+                FriendlyName="eduPersonEntitlement">
+                <saml:AttributeValue>
+                    https://ServiceProvider.com/entitlements/123456789
+                </saml:AttributeValue>
+            </RequestedAttribute>
+        </AttributeConsumingService>
+    </SPSSODescriptor>
+</EntityDescriptor>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-SPSSODescriptorType-protocolSupportEnumeration.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-SPSSODescriptorType-protocolSupportEnumeration.xml
new file mode 100644
index 0000000..492cca8
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/missing-attribute/saml20-SPSSODescriptorType-protocolSupportEnumeration.xml
@@ -0,0 +1,9 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  entityID="https://ServiceProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <SPSSODescriptor AuthnRequestsSigned="true">
+
+    </SPSSODescriptor>
+</EntityDescriptor>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-entities-descriptor.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-entities-descriptor.xml
new file mode 100644
index 0000000..c3b7062
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-entities-descriptor.xml
@@ -0,0 +1,240 @@
+<EntitiesDescriptor Name="https://your-federation.org/metadata/federation-name.xml"
+                    xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                    xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
+                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <EntityDescriptor entityID="https://idp.example.org/idp/shibboleth">
+
+        <IDPSSODescriptor protocolSupportEnumeration="urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
+            <Extensions>
+                <shibmd:Scope regexp="false">example.org</shibmd:Scope>
+            </Extensions>
+            <KeyDescriptor>
+                <ds:KeyInfo>
+                    <ds:X509Data>
+                        <ds:X509Certificate>...</ds:X509Certificate>
+                    </ds:X509Data>
+                </ds:KeyInfo>
+            </KeyDescriptor>
+
+            <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+            <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+
+            <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"
+                                 Location="https://idp.example.org/idp/profile/Shibboleth/SSO" />
+
+            <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+                                 Location="https://idp.example.org/idp/profile/SAML2/POST/SSO" />
+
+            <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+                                 Location="https://idp.example.org/idp/profile/SAML2/Redirect/SSO" />
+        </IDPSSODescriptor>
+
+        <AttributeAuthorityDescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
+
+            <KeyDescriptor>
+                <ds:KeyInfo>
+                    <ds:X509Data>
+                        <ds:X509Certificate>...</ds:X509Certificate>
+                    </ds:X509Data>
+                </ds:KeyInfo>
+            </KeyDescriptor>
+
+            <AttributeService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+                              Location="https://idp.example.org:8443/idp/profile/SAML1/SOAP/AttributeQuery" />
+
+            <AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+                              Location="https://idp.example.org:8443/idp/profile/SAML2/SOAP/AttributeQuery" />
+
+            <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+            <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+
+        </AttributeAuthorityDescriptor>
+
+        <Organization>
+            <OrganizationName xml:lang="en">Your Identities</OrganizationName>
+            <OrganizationDisplayName xml:lang="en"> Your Identities</OrganizationDisplayName>
+            <OrganizationURL xml:lang="en">http://www.example.org/</OrganizationURL>
+        </Organization>
+        <ContactPerson contactType="technical">
+            <GivenName>Your</GivenName>
+            <SurName>Contact</SurName>
+            <EmailAddress>admin@example.org</EmailAddress>
+        </ContactPerson>
+
+    </EntityDescriptor>
+
+    <!-- A service provider. -->
+    <EntityDescriptor entityID="https://sp.example.org/shibboleth-sp">
+        <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol">
+
+            <Extensions>
+                <idpdisc:DiscoveryResponse xmlns:idpdisc="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+                                           index="1" Binding="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+                                           Location="http://sp.example.org/Shibboleth.sso/DS"/>
+                <idpdisc:DiscoveryResponse xmlns:idpdisc="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+                                           index="2" Binding="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+                                           Location="https://sp.example.org/Shibboleth.sso/DS"/>
+            </Extensions>
+
+            <KeyDescriptor>
+                <ds:KeyInfo>
+                    <ds:X509Data>
+                        <ds:X509Certificate>...</ds:X509Certificate>
+                    </ds:X509Data>
+                </ds:KeyInfo>
+            </KeyDescriptor>
+
+            <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+            <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+
+            <AssertionConsumerService index="1" isDefault="true"
+                                      Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+                                      Location="https://sp.example.org/Shibboleth.sso/SAML2/POST"/>
+            <AssertionConsumerService index="2"
+                                      Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
+                                      Location="https://sp.example.org/Shibboleth.sso/SAML2/POST-SimpleSign"/>
+            <AssertionConsumerService index="3"
+                                      Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
+                                      Location="https://sp.example.org/Shibboleth.sso/SAML2/Artifact"/>
+            <AssertionConsumerService index="4"
+                                      Binding="urn:oasis:names:tc:SAML:1.0:profiles:browser-post"
+                                      Location="https://sp.example.org/Shibboleth.sso/SAML/POST"/>
+            <AssertionConsumerService index="5"
+                                      Binding="urn:oasis:names:tc:SAML:1.0:profiles:artifact-01"
+                                      Location="https://sp.example.org/Shibboleth.sso/SAML/Artifact"/>
+
+        </SPSSODescriptor>
+
+        <Organization>
+            <OrganizationName xml:lang="en">Your Service</OrganizationName>
+            <OrganizationDisplayName xml:lang="en">Your Service</OrganizationDisplayName>
+            <OrganizationURL xml:lang="en">http://sp.example.org/</OrganizationURL>
+        </Organization>
+        <ContactPerson contactType="technical">
+            <GivenName>Your</GivenName>
+            <SurName>Admin</SurName>
+            <EmailAddress>admin@example.org</EmailAddress>
+        </ContactPerson>
+
+    </EntityDescriptor>
+
+    <EntitiesDescriptor>
+        <EntityDescriptor entityID="https://idp.example.org/idp/shibboleth">
+
+            <IDPSSODescriptor protocolSupportEnumeration="urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
+                <Extensions>
+                    <shibmd:Scope regexp="false">example.org</shibmd:Scope>
+                </Extensions>
+                <KeyDescriptor>
+                    <ds:KeyInfo>
+                        <ds:X509Data>
+                            <ds:X509Certificate>...</ds:X509Certificate>
+                        </ds:X509Data>
+                    </ds:KeyInfo>
+                </KeyDescriptor>
+
+                <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+                <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+
+                <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"
+                                     Location="https://idp.example.org/idp/profile/Shibboleth/SSO" />
+
+                <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+                                     Location="https://idp.example.org/idp/profile/SAML2/POST/SSO" />
+
+                <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+                                     Location="https://idp.example.org/idp/profile/SAML2/Redirect/SSO" />
+            </IDPSSODescriptor>
+
+            <AttributeAuthorityDescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
+
+                <KeyDescriptor>
+                    <ds:KeyInfo>
+                        <ds:X509Data>
+                            <ds:X509Certificate>...</ds:X509Certificate>
+                        </ds:X509Data>
+                    </ds:KeyInfo>
+                </KeyDescriptor>
+
+                <AttributeService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+                                  Location="https://idp.example.org:8443/idp/profile/SAML1/SOAP/AttributeQuery" />
+
+                <AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+                                  Location="https://idp.example.org:8443/idp/profile/SAML2/SOAP/AttributeQuery" />
+
+                <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+                <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+
+            </AttributeAuthorityDescriptor>
+
+            <Organization>
+                <OrganizationName xml:lang="en">Your Identities</OrganizationName>
+                <OrganizationDisplayName xml:lang="en"> Your Identities</OrganizationDisplayName>
+                <OrganizationURL xml:lang="en">http://www.example.org/</OrganizationURL>
+            </Organization>
+            <ContactPerson contactType="technical">
+                <GivenName>Your</GivenName>
+                <SurName>Contact</SurName>
+                <EmailAddress>admin@example.org</EmailAddress>
+            </ContactPerson>
+
+        </EntityDescriptor>
+
+        <!-- A service provider. -->
+        <EntityDescriptor entityID="https://sp.example.org/shibboleth-sp">
+            <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol">
+
+                <Extensions>
+                    <idpdisc:DiscoveryResponse xmlns:idpdisc="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+                                               index="1" Binding="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+                                               Location="http://sp.example.org/Shibboleth.sso/DS"/>
+                    <idpdisc:DiscoveryResponse xmlns:idpdisc="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+                                               index="2" Binding="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+                                               Location="https://sp.example.org/Shibboleth.sso/DS"/>
+                </Extensions>
+
+                <KeyDescriptor>
+                    <ds:KeyInfo>
+                        <ds:X509Data>
+                            <ds:X509Certificate>...</ds:X509Certificate>
+                        </ds:X509Data>
+                    </ds:KeyInfo>
+                </KeyDescriptor>
+
+                <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+                <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+
+                <AssertionConsumerService index="1" isDefault="true"
+                                          Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+                                          Location="https://sp.example.org/Shibboleth.sso/SAML2/POST"/>
+                <AssertionConsumerService index="2"
+                                          Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
+                                          Location="https://sp.example.org/Shibboleth.sso/SAML2/POST-SimpleSign"/>
+                <AssertionConsumerService index="3"
+                                          Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
+                                          Location="https://sp.example.org/Shibboleth.sso/SAML2/Artifact"/>
+                <AssertionConsumerService index="4"
+                                          Binding="urn:oasis:names:tc:SAML:1.0:profiles:browser-post"
+                                          Location="https://sp.example.org/Shibboleth.sso/SAML/POST"/>
+                <AssertionConsumerService index="5"
+                                          Binding="urn:oasis:names:tc:SAML:1.0:profiles:artifact-01"
+                                          Location="https://sp.example.org/Shibboleth.sso/SAML/Artifact"/>
+
+            </SPSSODescriptor>
+
+            <Organization>
+                <OrganizationName xml:lang="en">Your Service</OrganizationName>
+                <OrganizationDisplayName xml:lang="en">Your Service</OrganizationDisplayName>
+                <OrganizationURL xml:lang="en">http://sp.example.org/</OrganizationURL>
+            </Organization>
+            <ContactPerson contactType="technical">
+                <GivenName>Your</GivenName>
+                <SurName>Admin</SurName>
+                <EmailAddress>admin@example.org</EmailAddress>
+            </ContactPerson>
+
+        </EntityDescriptor>
+    </EntitiesDescriptor>
+
+</EntitiesDescriptor>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-entity-descriptor-authn-authority.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-entity-descriptor-authn-authority.xml
new file mode 100644
index 0000000..47cd9e6
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-entity-descriptor-authn-authority.xml
@@ -0,0 +1,58 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
+                  entityID="https://IdentityProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <AuthnAuthorityDescriptor ID="ID" protocolSupportEnumeration="http://www.example.com/ http://www.example2.com/">
+        <ds:Signature>
+            <ds:SignedInfo>
+                <ds:CanonicalizationMethod Algorithm="http://www.example.com/">...</ds:CanonicalizationMethod>
+                <ds:SignatureMethod Algorithm="http://www.example.com/">...</ds:SignatureMethod>
+                <ds:Reference URI="http://www.example.com/">...</ds:Reference>
+            </ds:SignedInfo>
+            <ds:SignatureValue>GpM7</ds:SignatureValue>
+            <ds:KeyInfo>
+                <ds:KeyName>string</ds:KeyName>
+            </ds:KeyInfo>
+            <ds:Object>...</ds:Object>
+        </ds:Signature>
+        <Extensions>...</Extensions>
+        <KeyDescriptor>
+            <ds:KeyInfo>
+                <ds:KeyName>string</ds:KeyName>
+            </ds:KeyInfo>
+            <EncryptionMethod Algorithm="http://www.example.com/">
+                <xenc:KeySize>1</xenc:KeySize>
+                <xenc:OAEPparams>GpM7</xenc:OAEPparams>
+            </EncryptionMethod>
+        </KeyDescriptor>
+        <Organization>
+            <Extensions>
+            </Extensions>
+            <OrganizationName xml:lang="en-US">string</OrganizationName>
+            <OrganizationDisplayName xml:lang="en-US">string</OrganizationDisplayName>
+            <OrganizationURL xml:lang="en-US">http://www.example.com/</OrganizationURL>
+        </Organization>
+        <ContactPerson contactType="technical">
+            <Extensions>...</Extensions>
+            <Company>string</Company>
+            <GivenName>string</GivenName>
+            <SurName>string</SurName>
+            <EmailAddress>http://www.example.com/</EmailAddress>
+            <TelephoneNumber>string</TelephoneNumber>
+        </ContactPerson>
+        <AuthnQueryService Binding="http://www.example.com/" Location="http://www.example.com/">...</AuthnQueryService>
+        <AssertionIDRequestService Binding="http://www.example.com/" Location="http://www.example.com/">...</AssertionIDRequestService>
+        <NameIDFormat>http://www.example.com/</NameIDFormat>
+    </AuthnAuthorityDescriptor>
+    <Organization>
+        <OrganizationName xml:lang="en">Identity Providers R
+            US</OrganizationName>
+        <OrganizationDisplayName xml:lang="en">
+            Identity Providers R US, a Division of Lerxst Corp.
+        </OrganizationDisplayName>
+        <OrganizationURL
+                xml:lang="en">https://IdentityProvider.com</OrganizationURL>
+    </Organization>
+</EntityDescriptor>
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-entity-descriptor-idp-invalid-end-element.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-entity-descriptor-idp-invalid-end-element.xml
new file mode 100644
index 0000000..6d92f62
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-entity-descriptor-idp-invalid-end-element.xml
@@ -0,0 +1,101 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  entityID="https://IdentityProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <IDPSSODescriptor WantAuthnRequestsSigned="true"
+                      protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <KeyDescriptor use="signing">
+            <ds:KeyInfo>
+                <ds:KeyName>IdentityProvider.com SSO Key</ds:KeyName>
+            </ds:KeyInfo>
+        </KeyDescriptor>
+        <ArtifactResolutionService isDefault="true" index="0"
+                                   Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+                                   Location="https://IdentityProvider.com/SAML/Artifact"/>
+        <SingleLogoutService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+            Location="https://IdentityProvider.com/SAML/SLO/SOAP"/>
+        <SingleLogoutService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+            Location="https://IdentityProvider.com/SAML/SLO/Browser"
+            ResponseLocation="https://IdentityProvider.com/SAML/SLO/Response"/>
+        <NameIDFormat>
+            urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+        </NameIDFormat>
+        <NameIDFormat>
+            urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
+        </NameIDFormat>
+        <NameIDFormat>
+            urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+        </Invalid-end-tag>
+        <SingleSignOnService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+            Location="https://IdentityProvider.com/SAML/SSO/Browser"/>
+        <SingleSignOnService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+            Location="https://IdentityProvider.com/SAML/SSO/Browser"/>
+        <saml:Attribute
+            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
+            Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
+            FriendlyName="eduPersonPrincipalName">
+        </saml:Attribute>
+        <saml:Attribute
+            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
+            Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1"
+            FriendlyName="eduPersonAffiliation">
+            <saml:AttributeValue>member</saml:AttributeValue>
+            <saml:AttributeValue>student</saml:AttributeValue>
+            <saml:AttributeValue>faculty</saml:AttributeValue>
+            <saml:AttributeValue>employee</saml:AttributeValue>
+            <saml:AttributeValue>staff</saml:AttributeValue>
+        </saml:Attribute>
+    </IDPSSODescriptor>
+    <AttributeAuthorityDescriptor
+        protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <KeyDescriptor use="signing">
+            <ds:KeyInfo>
+                <ds:KeyName>IdentityProvider.com AA Key</ds:KeyName>
+            </ds:KeyInfo>
+        </KeyDescriptor>
+        <AttributeService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+            Location="https://IdentityProvider.com/SAML/AA/SOAP"/>
+        <AssertionIDRequestService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:URI"
+            Location="https://IdentityProvider.com/SAML/AA/URI"/>
+        <NameIDFormat>
+            urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+        </NameIDFormat>
+        <NameIDFormat>
+            urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
+        </NameIDFormat>
+        <NameIDFormat>
+            urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+        </NameIDFormat>
+        <saml:Attribute
+            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
+            Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
+            FriendlyName="eduPersonPrincipalName">
+        </saml:Attribute>
+        <saml:Attribute
+            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
+            Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1"
+            FriendlyName="eduPersonAffiliation">
+            <saml:AttributeValue>member</saml:AttributeValue>
+            <saml:AttributeValue>student</saml:AttributeValue>
+            <saml:AttributeValue>faculty</saml:AttributeValue>
+            <saml:AttributeValue>employee</saml:AttributeValue>
+            <saml:AttributeValue>staff</saml:AttributeValue>
+        </saml:Attribute>
+    </AttributeAuthorityDescriptor>
+    <Organization>
+        <OrganizationName xml:lang="en">Identity Providers R
+            US</OrganizationName>
+        <OrganizationDisplayName xml:lang="en">
+            Identity Providers R US, a Division of Lerxst Corp.
+        </OrganizationDisplayName>
+        <OrganizationURL
+            xml:lang="en">https://IdentityProvider.com</OrganizationURL>
+    </Organization>
+</EntityDescriptor>
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-entity-descriptor-pdp.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-entity-descriptor-pdp.xml
new file mode 100644
index 0000000..a0518c3
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-entity-descriptor-pdp.xml
@@ -0,0 +1,58 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+                  xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
+                  entityID="https://IdentityProvider.com/SAML">
+    <ds:Signature>...</ds:Signature>
+    <PDPDescriptor ID="ID" protocolSupportEnumeration="http://www.example.com/ http://www.example.com/">
+        <ds:Signature>
+            <ds:SignedInfo>
+                <ds:CanonicalizationMethod Algorithm="http://www.example.com/">...</ds:CanonicalizationMethod>
+                <ds:SignatureMethod Algorithm="http://www.example.com/">...</ds:SignatureMethod>
+                <ds:Reference URI="http://www.example.com/">...</ds:Reference>
+            </ds:SignedInfo>
+            <ds:SignatureValue>GpM7</ds:SignatureValue>
+            <ds:KeyInfo>
+                <ds:KeyName>string</ds:KeyName>
+            </ds:KeyInfo>
+            <ds:Object>Any text, intermingled with:</ds:Object>
+        </ds:Signature>
+        <Extensions>
+        </Extensions>
+        <KeyDescriptor>
+            <ds:KeyInfo>
+                <ds:KeyName>string</ds:KeyName>
+            </ds:KeyInfo>
+            <EncryptionMethod Algorithm="http://www.example.com/">
+                <xenc:KeySize>1</xenc:KeySize>
+                <xenc:OAEPparams>GpM7</xenc:OAEPparams>
+            </EncryptionMethod>
+        </KeyDescriptor>
+        <Organization>
+            <OrganizationName xml:lang="en-US">string</OrganizationName>
+            <OrganizationDisplayName xml:lang="en-US">string</OrganizationDisplayName>
+            <OrganizationURL xml:lang="en-US">http://www.example.com/</OrganizationURL>
+        </Organization>
+        <ContactPerson contactType="technical">
+            <Company>string</Company>
+            <GivenName>string</GivenName>
+            <SurName>string</SurName>
+            <EmailAddress>http://www.example.com/</EmailAddress>
+            <TelephoneNumber>string</TelephoneNumber>
+        </ContactPerson>
+        <AuthzService Binding="http://www.example.com/" Location="http://www.example.com/">
+        </AuthzService>
+        <AssertionIDRequestService Binding="http://www.example.com/" Location="http://www.example.com/">
+        </AssertionIDRequestService>
+        <NameIDFormat>http://www.example.com/</NameIDFormat>
+    </PDPDescriptor>
+    <Organization>
+        <OrganizationName xml:lang="en">Identity Providers R
+            US</OrganizationName>
+        <OrganizationDisplayName xml:lang="en">
+            Identity Providers R US, a Division of Lerxst Corp.
+        </OrganizationDisplayName>
+        <OrganizationURL
+                xml:lang="en">https://IdentityProvider.com</OrganizationURL>
+    </Organization>
+</EntityDescriptor>
diff --git a/saml-core-api/src/main/java/org/keycloak/dom/saml/v2/metadata/ExtensionsType.java b/saml-core-api/src/main/java/org/keycloak/dom/saml/v2/metadata/ExtensionsType.java
index 364c90e..3420167 100755
--- a/saml-core-api/src/main/java/org/keycloak/dom/saml/v2/metadata/ExtensionsType.java
+++ b/saml-core-api/src/main/java/org/keycloak/dom/saml/v2/metadata/ExtensionsType.java
@@ -18,6 +18,10 @@ package org.keycloak.dom.saml.v2.metadata;
 
 import org.w3c.dom.Element;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 /**
  * <p>
  * Java class for ExtensionsType complex type.
@@ -39,13 +43,49 @@ import org.w3c.dom.Element;
  */
 public class ExtensionsType {
 
-    protected Element element = null;
+    protected List<Object> any = new ArrayList<Object>();
 
+    /**
+     * Function is obsoleted with getAny
+     * @return
+     */
+    @Deprecated
     public Element getElement() {
-        return element;
+        return (any.isEmpty()) ? null : (Element) any.get(0);
     }
 
+    /**
+     * Function is obsoleted with addExtension
+     * @return
+     */
+    @Deprecated
     public void setElement(Element element) {
-        this.element = element;
+        any.clear();
+        any.add(element);
+    }
+
+    /**
+     * Add an extension
+     *
+     * @param extension
+     */
+    public void addExtension(Object extension) {
+        any.add(extension);
+    }
+
+    /**
+     * Remove an extension
+     *
+     * @param extension
+     */
+    public void removeExtension(Object extension) {
+        any.remove(extension);
+    }
+
+    /**
+     * Gets the value of the any property.
+     */
+    public List<Object> getAny() {
+        return Collections.unmodifiableList(this.any);
     }
 }
\ No newline at end of file
diff --git a/saml-core-api/src/main/java/org/keycloak/dom/xmlsec/w3/xmlenc/EncryptionMethodType.java b/saml-core-api/src/main/java/org/keycloak/dom/xmlsec/w3/xmlenc/EncryptionMethodType.java
index a88273d..2d3e82b 100755
--- a/saml-core-api/src/main/java/org/keycloak/dom/xmlsec/w3/xmlenc/EncryptionMethodType.java
+++ b/saml-core-api/src/main/java/org/keycloak/dom/xmlsec/w3/xmlenc/EncryptionMethodType.java
@@ -56,6 +56,11 @@ public class EncryptionMethodType {
             OAEPparams = oAEPparams;
         }
 
+        public EncryptionMethod() {
+            this.keySize = null;
+            this.OAEPparams = null;
+        }
+
         public BigInteger getKeySize() {
             return keySize;
         }
@@ -63,6 +68,14 @@ public class EncryptionMethodType {
         public byte[] getOAEPparams() {
             return OAEPparams;
         }
+
+        public void setKeySize(BigInteger keySize) {
+            this.keySize = keySize;
+        }
+
+        public void setOAEPparams(byte[] OAEPparams) {
+            this.OAEPparams = OAEPparams;
+        }
     }
 
     public EncryptionMethodType(String algo) {