saml adapter schema and simplifications

This commit is contained in:
Bill Burke 2015-10-02 18:39:50 -04:00
parent 8acdaf462b
commit d06237a3fd
18 changed files with 318 additions and 92 deletions

View file

@ -210,7 +210,6 @@ public class DefaultSamlDeployment implements SamlDeployment {
private KeyPair signingKeyPair; private KeyPair signingKeyPair;
private String assertionConsumerServiceUrl; private String assertionConsumerServiceUrl;
private Set<String> roleAttributeNames; private Set<String> roleAttributeNames;
private Set<String> roleFriendlyAttributeNames;
private PrincipalNamePolicy principalNamePolicy = PrincipalNamePolicy.FROM_NAME_ID; private PrincipalNamePolicy principalNamePolicy = PrincipalNamePolicy.FROM_NAME_ID;
private String principalAttributeName; private String principalAttributeName;
private String logoutPage; private String logoutPage;
@ -268,11 +267,6 @@ public class DefaultSamlDeployment implements SamlDeployment {
return roleAttributeNames; return roleAttributeNames;
} }
@Override
public Set<String> getRoleAttributeFriendlyNames() {
return roleFriendlyAttributeNames;
}
@Override @Override
public PrincipalNamePolicy getPrincipalNamePolicy() { public PrincipalNamePolicy getPrincipalNamePolicy() {
return principalNamePolicy; return principalNamePolicy;
@ -323,10 +317,6 @@ public class DefaultSamlDeployment implements SamlDeployment {
this.roleAttributeNames = roleAttributeNames; this.roleAttributeNames = roleAttributeNames;
} }
public void setRoleFriendlyAttributeNames(Set<String> roleFriendlyAttributeNames) {
this.roleFriendlyAttributeNames = roleFriendlyAttributeNames;
}
public void setPrincipalNamePolicy(PrincipalNamePolicy principalNamePolicy) { public void setPrincipalNamePolicy(PrincipalNamePolicy principalNamePolicy) {
this.principalNamePolicy = principalNamePolicy; this.principalNamePolicy = principalNamePolicy;
} }

View file

@ -354,7 +354,7 @@ public abstract class SamlAuthenticator {
} }
protected boolean isRole(AttributeType attribute) { protected boolean isRole(AttributeType attribute) {
return deployment.getRoleAttributeNames().contains(attribute.getName()) || deployment.getRoleAttributeFriendlyNames().contains(attribute.getFriendlyName()); return (attribute.getName() != null && deployment.getRoleAttributeNames().contains(attribute.getName())) || (attribute.getFriendlyName() != null && deployment.getRoleAttributeNames().contains(attribute.getFriendlyName()));
} }
protected AuthOutcome handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) { protected AuthOutcome handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) {

View file

@ -64,7 +64,6 @@ public interface SamlDeployment {
String getLogoutPage(); String getLogoutPage();
Set<String> getRoleAttributeNames(); Set<String> getRoleAttributeNames();
Set<String> getRoleAttributeFriendlyNames();
enum PrincipalNamePolicy { enum PrincipalNamePolicy {
FROM_NAME_ID, FROM_NAME_ID,

View file

@ -134,6 +134,8 @@ public class IDP implements Serializable {
} }
private String entityID; private String entityID;
private String signatureAlgorithm;
private String signatureCanonicalizationMethod;
private SingleSignOnService singleSignOnService; private SingleSignOnService singleSignOnService;
private SingleLogoutService singleLogoutService; private SingleLogoutService singleLogoutService;
private List<Key> keys; private List<Key> keys;
@ -169,4 +171,21 @@ public class IDP implements Serializable {
public void setKeys(List<Key> keys) { public void setKeys(List<Key> keys) {
this.keys = keys; this.keys = keys;
} }
public String getSignatureAlgorithm() {
return signatureAlgorithm;
}
public void setSignatureAlgorithm(String signatureAlgorithm) {
this.signatureAlgorithm = signatureAlgorithm;
}
public String getSignatureCanonicalizationMethod() {
return signatureCanonicalizationMethod;
}
public void setSignatureCanonicalizationMethod(String signatureCanonicalizationMethod) {
this.signatureCanonicalizationMethod = signatureCanonicalizationMethod;
}
} }

View file

@ -41,9 +41,6 @@ public class SP implements Serializable {
private String nameIDPolicyFormat; private String nameIDPolicyFormat;
private PrincipalNameMapping principalNameMapping; private PrincipalNameMapping principalNameMapping;
private Set<String> roleAttributes; private Set<String> roleAttributes;
private Set<String> roleFriendlyAttributes;
private String signatureAlgorithm;
private String signatureCanonicalizationMethod;
private IDP idp; private IDP idp;
public String getEntityID() { public String getEntityID() {
@ -102,14 +99,6 @@ public class SP implements Serializable {
this.roleAttributes = roleAttributes; this.roleAttributes = roleAttributes;
} }
public Set<String> getRoleFriendlyAttributes() {
return roleFriendlyAttributes;
}
public void setRoleFriendlyAttributes(Set<String> roleFriendlyAttributes) {
this.roleFriendlyAttributes = roleFriendlyAttributes;
}
public IDP getIdp() { public IDP getIdp() {
return idp; return idp;
} }
@ -126,19 +115,4 @@ public class SP implements Serializable {
this.logoutPage = logoutPage; this.logoutPage = logoutPage;
} }
public String getSignatureAlgorithm() {
return signatureAlgorithm;
}
public void setSignatureAlgorithm(String signatureAlgorithm) {
this.signatureAlgorithm = signatureAlgorithm;
}
public String getSignatureCanonicalizationMethod() {
return signatureCanonicalizationMethod;
}
public void setSignatureCanonicalizationMethod(String signatureCanonicalizationMethod) {
this.signatureCanonicalizationMethod = signatureCanonicalizationMethod;
}
} }

View file

@ -39,10 +39,10 @@ public class ConfigXmlConstants {
public static final String ROLE_MAPPING_ELEMENT = "RoleMapping"; public static final String ROLE_MAPPING_ELEMENT = "RoleMapping";
public static final String ATTRIBUTE_ELEMENT = "Attribute"; public static final String ATTRIBUTE_ELEMENT = "Attribute";
public static final String FRIENDLY_ATTRIBUTE_ELEMENT = "FriendlyAttribute";
public static final String NAME_ATTR = "name"; public static final String NAME_ATTR = "name";
public static final String IDP_ELEMENT = "IDP"; public static final String IDP_ELEMENT = "IDP";
public static final String SIGNATURES_REQUIRED_ATTR = "signaturesRequired";
public static final String SINGLE_SIGN_ON_SERVICE_ELEMENT = "SingleSignOnService"; public static final String SINGLE_SIGN_ON_SERVICE_ELEMENT = "SingleSignOnService";
public static final String SINGLE_LOGOUT_SERVICE_ELEMENT = "SingleLogoutService"; public static final String SINGLE_LOGOUT_SERVICE_ELEMENT = "SingleLogoutService";
public static final String SIGN_REQUEST_ATTR = "signRequest"; public static final String SIGN_REQUEST_ATTR = "signRequest";

View file

@ -41,10 +41,10 @@ public class DeploymentBuilder {
deployment.setForceAuthentication(sp.isForceAuthentication()); deployment.setForceAuthentication(sp.isForceAuthentication());
deployment.setNameIDPolicyFormat(sp.getNameIDPolicyFormat()); deployment.setNameIDPolicyFormat(sp.getNameIDPolicyFormat());
deployment.setLogoutPage(sp.getLogoutPage()); deployment.setLogoutPage(sp.getLogoutPage());
deployment.setSignatureCanonicalizationMethod(sp.getSignatureCanonicalizationMethod()); deployment.setSignatureCanonicalizationMethod(sp.getIdp().getSignatureCanonicalizationMethod());
deployment.setSignatureAlgorithm(SignatureAlgorithm.RSA_SHA256); deployment.setSignatureAlgorithm(SignatureAlgorithm.RSA_SHA256);
if (sp.getSignatureAlgorithm() != null) { if (sp.getIdp().getSignatureAlgorithm() != null) {
deployment.setSignatureAlgorithm(SignatureAlgorithm.valueOf(sp.getSignatureAlgorithm())); deployment.setSignatureAlgorithm(SignatureAlgorithm.valueOf(sp.getIdp().getSignatureAlgorithm()));
} }
if (sp.getPrincipalNameMapping() != null) { if (sp.getPrincipalNameMapping() != null) {
SamlDeployment.PrincipalNamePolicy policy = SamlDeployment.PrincipalNamePolicy.valueOf(sp.getPrincipalNameMapping().getPolicy()); SamlDeployment.PrincipalNamePolicy policy = SamlDeployment.PrincipalNamePolicy.valueOf(sp.getPrincipalNameMapping().getPolicy());
@ -52,7 +52,6 @@ public class DeploymentBuilder {
deployment.setPrincipalAttributeName(sp.getPrincipalNameMapping().getAttributeName()); deployment.setPrincipalAttributeName(sp.getPrincipalNameMapping().getAttributeName());
} }
deployment.setRoleAttributeNames(sp.getRoleAttributes()); deployment.setRoleAttributeNames(sp.getRoleAttributes());
deployment.setRoleFriendlyAttributeNames(sp.getRoleFriendlyAttributes());
if (sp.getSslPolicy() != null) { if (sp.getSslPolicy() != null) {
SslRequired ssl = SslRequired.valueOf(sp.getSslPolicy()); SslRequired ssl = SslRequired.valueOf(sp.getSslPolicy());
deployment.setSslRequired(ssl); deployment.setSslRequired(ssl);

View file

@ -30,6 +30,10 @@ public class IDPXmlParser extends AbstractParser {
} }
idp.setEntityID(entityID); idp.setEntityID(entityID);
boolean signaturesRequired = StaxParserUtil.getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNATURES_REQUIRED_ATTR);
idp.setSignatureCanonicalizationMethod(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
idp.setSignatureAlgorithm(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
while (xmlEventReader.hasNext()) { while (xmlEventReader.hasNext()) {
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader); XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
if (xmlEvent == null) if (xmlEvent == null)
@ -47,11 +51,11 @@ public class IDPXmlParser extends AbstractParser {
break; break;
String tag = StaxParserUtil.getStartElementName(startElement); String tag = StaxParserUtil.getStartElementName(startElement);
if (tag.equals(ConfigXmlConstants.SINGLE_SIGN_ON_SERVICE_ELEMENT)) { if (tag.equals(ConfigXmlConstants.SINGLE_SIGN_ON_SERVICE_ELEMENT)) {
IDP.SingleSignOnService sso = parseSingleSignOnService(xmlEventReader); IDP.SingleSignOnService sso = parseSingleSignOnService(xmlEventReader, signaturesRequired);
idp.setSingleSignOnService(sso); idp.setSingleSignOnService(sso);
} else if (tag.equals(ConfigXmlConstants.SINGLE_LOGOUT_SERVICE_ELEMENT)) { } else if (tag.equals(ConfigXmlConstants.SINGLE_LOGOUT_SERVICE_ELEMENT)) {
IDP.SingleLogoutService slo = parseSingleLogoutService(xmlEventReader); IDP.SingleLogoutService slo = parseSingleLogoutService(xmlEventReader, signaturesRequired);
idp.setSingleLogoutService(slo); idp.setSingleLogoutService(slo);
} else if (tag.equals(ConfigXmlConstants.KEYS_ELEMENT)) { } else if (tag.equals(ConfigXmlConstants.KEYS_ELEMENT)) {
@ -66,25 +70,25 @@ public class IDPXmlParser extends AbstractParser {
return idp; return idp;
} }
protected IDP.SingleLogoutService parseSingleLogoutService(XMLEventReader xmlEventReader) throws ParsingException { protected IDP.SingleLogoutService parseSingleLogoutService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
IDP.SingleLogoutService slo = new IDP.SingleLogoutService(); IDP.SingleLogoutService slo = new IDP.SingleLogoutService();
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader); StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
slo.setSignRequest(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR)); slo.setSignRequest(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
slo.setValidateResponseSignature(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR)); slo.setValidateResponseSignature(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
slo.setValidateRequestSignature(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR)); slo.setValidateRequestSignature(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR, signaturesRequired));
slo.setRequestBinding(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR)); slo.setRequestBinding(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
slo.setResponseBinding(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR)); slo.setResponseBinding(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
slo.setSignResponse(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR)); slo.setSignResponse(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR, signaturesRequired));
slo.setPostBindingUrl(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR)); slo.setPostBindingUrl(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR));
slo.setRedirectBindingUrl(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR)); slo.setRedirectBindingUrl(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR));
return slo; return slo;
} }
protected IDP.SingleSignOnService parseSingleSignOnService(XMLEventReader xmlEventReader) throws ParsingException { protected IDP.SingleSignOnService parseSingleSignOnService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
IDP.SingleSignOnService sso = new IDP.SingleSignOnService(); IDP.SingleSignOnService sso = new IDP.SingleSignOnService();
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader); StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
sso.setSignRequest(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR)); sso.setSignRequest(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
sso.setValidateResponseSignature(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR)); sso.setValidateResponseSignature(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
sso.setRequestBinding(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR)); sso.setRequestBinding(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
sso.setResponseBinding(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR)); sso.setResponseBinding(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
sso.setBindingUrl(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR)); sso.setBindingUrl(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));

View file

@ -7,6 +7,8 @@ import org.keycloak.saml.common.util.StaxParserUtil;
import javax.xml.namespace.QName; import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.EndElement; import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement; import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent; import javax.xml.stream.events.XMLEvent;

View file

@ -37,8 +37,6 @@ public class SPXmlParser extends AbstractParser {
sp.setSslPolicy(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.SSL_POLICY_ATTR)); sp.setSslPolicy(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.SSL_POLICY_ATTR));
sp.setLogoutPage(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.LOGOUT_PAGE_ATTR)); sp.setLogoutPage(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.LOGOUT_PAGE_ATTR));
sp.setNameIDPolicyFormat(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.NAME_ID_POLICY_FORMAT_ATTR)); sp.setNameIDPolicyFormat(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.NAME_ID_POLICY_FORMAT_ATTR));
sp.setSignatureCanonicalizationMethod(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
sp.setSignatureAlgorithm(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
sp.setForceAuthentication(StaxParserUtil.getBooleanAttributeValue(startElement, ConfigXmlConstants.FORCE_AUTHENTICATION_ATTR)); sp.setForceAuthentication(StaxParserUtil.getBooleanAttributeValue(startElement, ConfigXmlConstants.FORCE_AUTHENTICATION_ATTR));
while (xmlEventReader.hasNext()) { while (xmlEventReader.hasNext()) {
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader); XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
@ -91,7 +89,6 @@ public class SPXmlParser extends AbstractParser {
StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader); StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
StaxParserUtil.validate(startElement, ConfigXmlConstants.ROLE_MAPPING_ELEMENT); StaxParserUtil.validate(startElement, ConfigXmlConstants.ROLE_MAPPING_ELEMENT);
Set<String> roleAttributes = new HashSet<>(); Set<String> roleAttributes = new HashSet<>();
Set<String> roleFriendlyAttributes = new HashSet<>();
while (xmlEventReader.hasNext()) { while (xmlEventReader.hasNext()) {
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader); XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
if (xmlEvent == null) if (xmlEvent == null)
@ -116,21 +113,12 @@ public class SPXmlParser extends AbstractParser {
} }
roleAttributes.add(attributeValue); roleAttributes.add(attributeValue);
} else if (tag.equals(ConfigXmlConstants.FRIENDLY_ATTRIBUTE_ELEMENT)) {
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
String attributeValue = StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.NAME_ATTR);
if (attributeValue == null) {
throw new ParsingException("RoleMapping FriendlyAttribute element must have the name attribute set");
}
roleFriendlyAttributes.add(attributeValue);
} else { } else {
StaxParserUtil.bypassElementBlock(xmlEventReader, tag); StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
} }
} }
sp.setRoleAttributes(roleAttributes); sp.setRoleAttributes(roleAttributes);
sp.setRoleFriendlyAttributes(roleFriendlyAttributes);
} }

View file

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="urn:keycloak:saml:adapter"
targetNamespace="urn:keycloak:saml:adapter"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:element name="keycloak-saml-adapter" type="adapter-type"/>
<xs:complexType name="adapter-type">
<xs:annotation>
<xs:documentation>
<![CDATA[
The Keycloak SAML Adapter keycloak-saml.xml config file
]]>
</xs:documentation>
</xs:annotation>
<xs:all>
<xs:element name="SP" maxOccurs="1" minOccurs="0" type="sp-type"/>
</xs:all>
</xs:complexType>
<xs:complexType name="sp-type">
<xs:all>
<xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1"/>
<xs:element name="PrincipalNameMapping" type="principal-name-mapping-type" minOccurs="0" maxOccurs="1"/>
<xs:element name="RoleMapping" type="role-mapping-type" minOccurs="0" maxOccurs="1"/>
<xs:element name="IDP" type="idp-type" minOccurs="1" maxOccurs="1"/>
</xs:all>
<xs:attribute name="entityID" type="xs:string" use="required"/>
<xs:attribute name="sslPolicy" type="xs:string" use="optional"/>
<xs:attribute name="nameIDPolicyFormat" type="xs:string" use="optional"/>
<xs:attribute name="logoutPage" type="xs:string" use="optional"/>
<xs:attribute name="forceAuthentication" type="xs:boolean" use="optional"/>
</xs:complexType>
<xs:complexType name="keys-type">
<xs:sequence>
<xs:element name="Key" type="key-type" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="key-type">
<xs:all>
<xs:element name="KeyStore" maxOccurs="1" minOccurs="0" type="key-store-type"/>
<xs:element name="PrivateKeyPem" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="PublicKeyPem" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="CertificatePem" type="xs:string" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="signing" type="xs:boolean" use="optional"/>
<xs:attribute name="encryption" type="xs:boolean" use="optional"/>
</xs:complexType>
<xs:complexType name="key-store-type">
<xs:all>
<xs:element name="PrivateKey" maxOccurs="1" minOccurs="0" type="private-key-type"/>
<xs:element name="Certificate" type="certificate-type" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="file" type="xs:string" use="optional"/>
<xs:attribute name="resource" type="xs:string" use="optional"/>
<xs:attribute name="password" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="private-key-type">
<xs:attribute name="alias" type="xs:string" use="required"/>
<xs:attribute name="password" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="certificate-type">
<xs:attribute name="alias" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="principal-name-mapping-type">
<xs:attribute name="policy" type="xs:string" use="required"/>
<xs:attribute name="attribute" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="role-mapping-type">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="Attribute" maxOccurs="unbounded" minOccurs="0" type="attribute-type"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="attribute-type">
<xs:attribute name="name" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="idp-type">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="SingleSignOnService" maxOccurs="1" minOccurs="1" type="sign-on-type"/>
<xs:element name="SingleLogoutService" type="logout-type" minOccurs="0" maxOccurs="1"/>
<xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute name="entityID" type="xs:string" use="required"/>
<xs:attribute name="signaturesRequired" type="xs:boolean" use="required"/>
<xs:attribute name="signatureAlgorithm" type="xs:string" use="optional"/>
<xs:attribute name="signatureCanonicalizationMethod" type="xs:string" use="optional"/>
<xs:attribute name="encryption" type="xs:boolean" use="optional"/>
</xs:complexType>
<xs:complexType name="sign-on-type">
<xs:attribute name="signRequest" type="xs:boolean" use="optional"/>
<xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional"/>
<xs:attribute name="requestBinding" type="xs:string" use="optional"/>
<xs:attribute name="responseBinding" type="xs:string" use="optional"/>
<xs:attribute name="bindingUrl" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="logout-type">
<xs:attribute name="signRequest" type="xs:boolean" use="optional"/>
<xs:attribute name="signResponse" type="xs:boolean" use="optional"/>
<xs:attribute name="validateRequestSignature" type="xs:boolean" use="optional"/>
<xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional"/>
<xs:attribute name="requestBinding" type="xs:string" use="optional"/>
<xs:attribute name="responseBinding" type="xs:string" use="optional"/>
<xs:attribute name="postBindingUrl" type="xs:string" use="optional"/>
<xs:attribute name="redirectBindingUrl" type="xs:string" use="optional"/>
</xs:complexType>
</xs:schema>

View file

@ -7,7 +7,15 @@ import org.keycloak.adapters.saml.config.Key;
import org.keycloak.adapters.saml.config.KeycloakSamlAdapter; import org.keycloak.adapters.saml.config.KeycloakSamlAdapter;
import org.keycloak.adapters.saml.config.SP; import org.keycloak.adapters.saml.config.SP;
import org.keycloak.adapters.saml.config.parsers.KeycloakSamlAdapterXMLParser; import org.keycloak.adapters.saml.config.parsers.KeycloakSamlAdapterXMLParser;
import org.keycloak.saml.common.util.StaxParserUtil;
import javax.xml.XMLConstants;
import javax.xml.stream.XMLEventReader;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.InputStream; import java.io.InputStream;
/** /**
@ -16,6 +24,37 @@ import java.io.InputStream;
*/ */
public class XmlParserTest { public class XmlParserTest {
@Test
public void testValidation() throws Exception {
{
InputStream schema = KeycloakSamlAdapterXMLParser.class.getResourceAsStream("/schema/keycloak_saml_adapter_1_6.xsd");
InputStream is = getClass().getResourceAsStream("/keycloak-saml.xml");
Assert.assertNotNull(is);
Assert.assertNotNull(schema);
StaxParserUtil.validate(is, schema);
}
{
InputStream sch = KeycloakSamlAdapterXMLParser.class.getResourceAsStream("/schema/keycloak_saml_adapter_1_6.xsd");
InputStream doc = getClass().getResourceAsStream("/keycloak-saml2.xml");
Assert.assertNotNull(doc);
Assert.assertNotNull(sch);
try {
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = factory.newSchema(new StreamSource(sch));
Validator validator = schema.newValidator();
StreamSource source = new StreamSource(doc);
source.setSystemId("/keycloak-saml2.xml");
validator.validate(source);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Test @Test
public void testXmlParser() throws Exception { public void testXmlParser() throws Exception {
InputStream is = getClass().getResourceAsStream("/keycloak-saml.xml"); InputStream is = getClass().getResourceAsStream("/keycloak-saml.xml");
@ -48,11 +87,11 @@ public class XmlParserTest {
Assert.assertEquals("attribute", sp.getPrincipalNameMapping().getAttributeName()); Assert.assertEquals("attribute", sp.getPrincipalNameMapping().getAttributeName());
Assert.assertTrue(sp.getRoleAttributes().size() == 1); Assert.assertTrue(sp.getRoleAttributes().size() == 1);
Assert.assertTrue(sp.getRoleAttributes().contains("member")); Assert.assertTrue(sp.getRoleAttributes().contains("member"));
Assert.assertTrue(sp.getRoleFriendlyAttributes().size() == 1);
Assert.assertTrue(sp.getRoleFriendlyAttributes().contains("memberOf"));
IDP idp = sp.getIdp(); IDP idp = sp.getIdp();
Assert.assertEquals("idp", idp.getEntityID()); Assert.assertEquals("idp", idp.getEntityID());
Assert.assertEquals("RSA", idp.getSignatureAlgorithm());
Assert.assertEquals("canon", idp.getSignatureCanonicalizationMethod());
Assert.assertTrue(idp.getSingleSignOnService().isSignRequest()); Assert.assertTrue(idp.getSingleSignOnService().isSignRequest());
Assert.assertTrue(idp.getSingleSignOnService().isValidateResponseSignature()); Assert.assertTrue(idp.getSingleSignOnService().isValidateResponseSignature());
Assert.assertEquals("post", idp.getSingleSignOnService().getRequestBinding()); Assert.assertEquals("post", idp.getSingleSignOnService().getRequestBinding());

View file

@ -1,9 +1,7 @@
<keycloak-saml-adapter> <keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter">
<SP entityID="sp" <SP entityID="sp"
sslPolicy="ssl" sslPolicy="ssl"
nameIDPolicyFormat="format" nameIDPolicyFormat="format"
signatureAlgorithm=""
sgnatureCanonicalizationMethod=""
forceAuthentication="true"> forceAuthentication="true">
<Keys> <Keys>
<Key signing="true" > <Key signing="true" >
@ -24,9 +22,12 @@
<PrincipalNameMapping policy="policy" attribute="attribute"/> <PrincipalNameMapping policy="policy" attribute="attribute"/>
<RoleMapping> <RoleMapping>
<Attribute name="member"/> <Attribute name="member"/>
<FriendlyAttribute name="memberOf"/>
</RoleMapping> </RoleMapping>
<IDP entityID="idp"> <IDP entityID="idp"
signatureAlgorithm="RSA"
signatureCanonicalizationMethod="canon"
signaturesRequired="true"
>
<SingleSignOnService signRequest="true" <SingleSignOnService signRequest="true"
validateResponseSignature="true" validateResponseSignature="true"
requestBinding="post" requestBinding="post"

View file

@ -0,0 +1,46 @@
<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter">
<SP entityID="sp"
sslPolicy="ssl"
nameIDPolicyFormat="format"
signatureAlgorithm=""
signatureCanonicalizationMethod=""
forceAuthentication="true">
<Keys>
<Key signing="true" >
<KeyStore file="file" resource="cp" password="pw">
<PrivateKey alias="private alias" password="private pw"/>
<Certificate alias="cert alias"/>
</KeyStore>
</Key>
<Key encryption="true">
<PrivateKeyPemmm>
private pem
</PrivateKeyPemmm>
<PublicKeyPem>
public pem
</PublicKeyPem>
</Key>
</Keys>
<PrincipalNameMapping policy="policy" attribute="attribute"/>
<RoleMapping>
<Attribute name="member"/>
</RoleMapping>
<IDP entityID="idp"
signaturesRequired="true"
>
<SingleSignOnService signRequest="true"
validateResponseSignature="true"
requestBinding="post"
bindingUrl="url"
/>
<Keys>
<Key signing="true">
<CertificatePem>
cert pem
</CertificatePem>
</Key>
</Keys>
</IDP>
</SP>
</keycloak-saml-adapter>

View file

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Template used by WildFly build when directed to include Keycloak subsystem in a configuration. -->
<config>
<extension-module>org.keycloak.keycloak-saml-adapter-subsystem</extension-module>
<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.6">
</subsystem>
</config>

View file

@ -27,13 +27,16 @@ import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.common.ErrorCodes; import org.keycloak.saml.common.ErrorCodes;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName; import javax.xml.namespace.QName;
import javax.xml.stream.Location; import javax.xml.stream.Location;
import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute; import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.EndElement; import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement; import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent; import javax.xml.stream.events.XMLEvent;
@ -41,7 +44,11 @@ import javax.xml.transform.Source;
import javax.xml.transform.Transformer; import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stax.StAXSource; import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator; import javax.xml.validation.Validator;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
/** /**
@ -54,7 +61,18 @@ public class StaxParserUtil {
private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger(); private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
protected static Validator validator = null; public static void validate(InputStream doc, InputStream sch) throws ParsingException {
try {
XMLEventReader xmlEventReader = StaxParserUtil.getXMLEventReader(doc);
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = factory.newSchema(new StreamSource(sch));
Validator validator = schema.newValidator();
validator.validate(new StAXSource(xmlEventReader));
} catch (Exception e) {
throw logger.parserException(e);
}
}
/** /**
* Bypass an entire XML element block from startElement to endElement * Bypass an entire XML element block from startElement to endElement
@ -75,6 +93,29 @@ public class StaxParserUtil {
} }
} }
/**
* Advances reader if character whitespace encountered
*
* @param xmlEventReader
* @param xmlEvent
* @return
*/
public static boolean wasWhitespacePeeked(XMLEventReader xmlEventReader, XMLEvent xmlEvent) {
if (xmlEvent.isCharacters()) {
Characters chars = xmlEvent.asCharacters();
String data = chars.getData();
if (data == null || data.trim().equals("")) {
try {
xmlEventReader.nextEvent();
return true;
} catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}
}
return false;
}
/** /**
* Given an {@code Attribute}, get its trimmed value * Given an {@code Attribute}, get its trimmed value
* *
@ -113,11 +154,23 @@ public class StaxParserUtil {
* @return false if attribute not set * @return false if attribute not set
*/ */
public static boolean getBooleanAttributeValue(StartElement startElement, String tag) { public static boolean getBooleanAttributeValue(StartElement startElement, String tag) {
return getBooleanAttributeValue(startElement, tag, false);
}
/**
* Get the Attribute value
*
* @param startElement
* @param tag localpart of the qname of the attribute
*
* @return false if attribute not set
*/
public static boolean getBooleanAttributeValue(StartElement startElement, String tag, boolean defaultValue) {
String result = null; String result = null;
Attribute attr = startElement.getAttributeByName(new QName(tag)); Attribute attr = startElement.getAttributeByName(new QName(tag));
if (attr != null) if (attr != null)
result = getAttributeValue(attr); result = getAttributeValue(attr);
if (result == null) return false; if (result == null) return defaultValue;
return Boolean.valueOf(result); return Boolean.valueOf(result);
} }

View file

@ -16,18 +16,13 @@
<RoleMapping> <RoleMapping>
<Attribute name="Role"/> <Attribute name="Role"/>
</RoleMapping> </RoleMapping>
<IDP entityID="idp"> <IDP entityID="idp"
<SingleSignOnService signRequest="true" signaturesRequired="true">
validateResponseSignature="true" <SingleSignOnService requestBinding="POST"
requestBinding="POST"
bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml" bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
/> />
<SingleLogoutService <SingleLogoutService
validateRequestSignature="true"
validateResponseSignature="true"
signRequest="true"
signResponse="true"
requestBinding="POST" requestBinding="POST"
responseBinding="POST" responseBinding="POST"
postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml" postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"

View file

@ -16,18 +16,13 @@
<RoleMapping> <RoleMapping>
<Attribute name="Role"/> <Attribute name="Role"/>
</RoleMapping> </RoleMapping>
<IDP entityID="idp"> <IDP entityID="idp"
<SingleSignOnService signRequest="true" signaturesRequired="true">
validateResponseSignature="true" <SingleSignOnService requestBinding="POST"
requestBinding="POST"
bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml" bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
/> />
<SingleLogoutService <SingleLogoutService
validateRequestSignature="true"
validateResponseSignature="true"
signRequest="true"
signResponse="true"
requestBinding="POST" requestBinding="POST"
responseBinding="POST" responseBinding="POST"
postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml" postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"