KEYCLOAK-14304 Enhance SAML Identity Provider Metadata processing
This commit is contained in:
parent
aad3bdcb88
commit
1fa93db1b4
14 changed files with 295 additions and 28 deletions
|
@ -0,0 +1,61 @@
|
||||||
|
package org.keycloak.dom.saml.v2.mdattr;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* *
|
||||||
|
* <p>
|
||||||
|
* Java class for EntityAttributes complex type.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The following schema fragment specifies the expected content contained within this class.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <element name="EntityAttributes" type="mdattr:EntityAttributesType"/>
|
||||||
|
* <complexType name="EntityAttributesType">
|
||||||
|
* <choice maxOccurs="unbounded">
|
||||||
|
* <element ref="saml:Attribute"/>
|
||||||
|
* <element ref="saml:Assertion"/>
|
||||||
|
* </sequence>
|
||||||
|
* </complexType>
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class EntityAttributes implements Serializable {
|
||||||
|
|
||||||
|
protected List<AttributeType> attribute = new ArrayList<>();
|
||||||
|
protected List<AssertionType> assertion = new ArrayList<>();
|
||||||
|
|
||||||
|
public List<AttributeType> getAttribute() {
|
||||||
|
return attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAttribute(AttributeType attributeType) {
|
||||||
|
attribute.add(attributeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAttribute(AttributeType attributeType) {
|
||||||
|
attribute.remove(attributeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AssertionType> getAssertion() {
|
||||||
|
return assertion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAssertion(AssertionType attributeType) {
|
||||||
|
assertion.add(attributeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAssertion(AttributeType attributeType) {
|
||||||
|
assertion.remove(attributeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,12 +16,13 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.dom.saml.v2.metadata;
|
package org.keycloak.dom.saml.v2.metadata;
|
||||||
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.dom.saml.v2.mdattr.EntityAttributes;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* Java class for ExtensionsType complex type.
|
* Java class for ExtensionsType complex type.
|
||||||
|
@ -88,4 +89,14 @@ public class ExtensionsType {
|
||||||
public List<Object> getAny() {
|
public List<Object> getAny() {
|
||||||
return Collections.unmodifiableList(this.any);
|
return Collections.unmodifiableList(this.any);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public EntityAttributes getEntityAttributes() {
|
||||||
|
for (Object o : this.any) {
|
||||||
|
if (o instanceof EntityAttributes) {
|
||||||
|
return (EntityAttributes) o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -65,6 +65,8 @@ public enum JBossSAMLURIConstants {
|
||||||
HOLDER_OF_KEY("urn:oasis:names:tc:SAML:2.0:cm:holder-of-key"),
|
HOLDER_OF_KEY("urn:oasis:names:tc:SAML:2.0:cm:holder-of-key"),
|
||||||
|
|
||||||
METADATA_NSURI("urn:oasis:names:tc:SAML:2.0:metadata"),
|
METADATA_NSURI("urn:oasis:names:tc:SAML:2.0:metadata"),
|
||||||
|
// http://docs.oasis-open.org/security/saml/Post2.0/sstc-metadata-attr-cd-01.pdf
|
||||||
|
METADATA_ENTITY_ATTRIBUTES_NSURI("urn:oasis:names:tc:SAML:metadata:attribute"),
|
||||||
|
|
||||||
NAMEID_FORMAT_TRANSIENT("urn:oasis:names:tc:SAML:2.0:nameid-format:transient"),
|
NAMEID_FORMAT_TRANSIENT("urn:oasis:names:tc:SAML:2.0:nameid-format:transient"),
|
||||||
NAMEID_FORMAT_PERSISTENT("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"),
|
NAMEID_FORMAT_PERSISTENT("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"),
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.keycloak.saml.processing.core.parsers.saml.mdattr;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import javax.xml.stream.XMLEventReader;
|
||||||
|
import javax.xml.stream.events.StartElement;
|
||||||
|
|
||||||
|
import org.keycloak.dom.saml.v2.mdattr.EntityAttributes;
|
||||||
|
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||||
|
import org.keycloak.saml.common.util.StaxParserUtil;
|
||||||
|
import org.keycloak.saml.processing.core.parsers.saml.assertion.SAMLAssertionParser;
|
||||||
|
import org.keycloak.saml.processing.core.parsers.saml.assertion.SAMLAttributeParser;
|
||||||
|
import org.keycloak.saml.processing.core.parsers.saml.metadata.AbstractStaxSamlMetadataParser;
|
||||||
|
import org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLMetadataQNames;
|
||||||
|
|
||||||
|
public class SAMLEntityAttributesParser extends AbstractStaxSamlMetadataParser<EntityAttributes> implements Serializable {
|
||||||
|
private static final SAMLEntityAttributesParser INSTANCE = new SAMLEntityAttributesParser();
|
||||||
|
|
||||||
|
private SAMLEntityAttributesParser() {
|
||||||
|
super(SAMLMetadataQNames.ENTITY_ATTRIBUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SAMLEntityAttributesParser getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected EntityAttributes instantiateElement(XMLEventReader xmlEventReader, StartElement element) throws ParsingException {
|
||||||
|
return new EntityAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processSubElement(XMLEventReader xmlEventReader, EntityAttributes target, SAMLMetadataQNames element,
|
||||||
|
StartElement elementDetail) throws ParsingException {
|
||||||
|
switch (element) {
|
||||||
|
case ATTRIBUTE:
|
||||||
|
target.addAttribute(SAMLAttributeParser.getInstance().parse(xmlEventReader));
|
||||||
|
break;
|
||||||
|
case ASSERTION:
|
||||||
|
target.addAssertion(SAMLAssertionParser.getInstance().parse(xmlEventReader));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw LOGGER.parserUnknownTag(StaxParserUtil.getElementName(elementDetail), elementDetail.getLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,12 +16,13 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.saml.processing.core.parsers.saml.metadata;
|
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.dom.saml.v2.metadata.ExtensionsType;
|
||||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||||
import org.keycloak.saml.common.util.StaxParserUtil;
|
import org.keycloak.saml.common.util.StaxParserUtil;
|
||||||
|
import org.keycloak.saml.processing.core.parsers.saml.mdattr.SAMLEntityAttributesParser;
|
||||||
import javax.xml.stream.XMLEventReader;
|
|
||||||
import javax.xml.stream.events.StartElement;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses <samlp:Extensions> SAML2 element into series of DOM nodes.
|
* Parses <samlp:Extensions> SAML2 element into series of DOM nodes.
|
||||||
|
@ -46,7 +47,16 @@ public class SAMLExtensionsParser extends AbstractStaxSamlMetadataParser<Extensi
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void processSubElement(XMLEventReader xmlEventReader, ExtensionsType target, SAMLMetadataQNames element, StartElement elementDetail) throws ParsingException {
|
protected void processSubElement(XMLEventReader xmlEventReader, ExtensionsType target, SAMLMetadataQNames element,
|
||||||
target.addExtension(StaxParserUtil.getDOMElement(xmlEventReader));
|
StartElement elementDetail) throws ParsingException {
|
||||||
|
|
||||||
|
switch (element) {
|
||||||
|
case ENTITY_ATTRIBUTES:
|
||||||
|
target.addExtension(SAMLEntityAttributesParser.getInstance().parse(xmlEventReader));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
target.addExtension(StaxParserUtil.getDOMElement(xmlEventReader));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,6 @@ public enum SAMLMetadataQNames implements HasQName {
|
||||||
ATTR_IS_REQUIRED(null, "isRequired"),
|
ATTR_IS_REQUIRED(null, "isRequired"),
|
||||||
ATTR_NAME(null, "Name"),
|
ATTR_NAME(null, "Name"),
|
||||||
ATTR_NAME_FORMAT(null, "NameFormat"),
|
ATTR_NAME_FORMAT(null, "NameFormat"),
|
||||||
|
|
||||||
// Elements from other namespaces that can be direct subelements of this namespace's elements
|
// Elements from other namespaces that can be direct subelements of this namespace's elements
|
||||||
SIGNATURE(XmlDSigQNames.SIGNATURE),
|
SIGNATURE(XmlDSigQNames.SIGNATURE),
|
||||||
KEY_INFO(XmlDSigQNames.KEY_INFO),
|
KEY_INFO(XmlDSigQNames.KEY_INFO),
|
||||||
|
@ -85,6 +84,8 @@ public enum SAMLMetadataQNames implements HasQName {
|
||||||
OAEP_PARAMS(JBossSAMLURIConstants.XMLENC_NSURI, "OAEPparams"),
|
OAEP_PARAMS(JBossSAMLURIConstants.XMLENC_NSURI, "OAEPparams"),
|
||||||
ATTR_X500_ENCODING(JBossSAMLURIConstants.X500_NSURI, "Encoding"),
|
ATTR_X500_ENCODING(JBossSAMLURIConstants.X500_NSURI, "Encoding"),
|
||||||
ATTRIBUTE(SAMLAssertionQNames.ATTRIBUTE),
|
ATTRIBUTE(SAMLAssertionQNames.ATTRIBUTE),
|
||||||
|
ASSERTION(SAMLAssertionQNames.ASSERTION),
|
||||||
|
ENTITY_ATTRIBUTES(JBossSAMLURIConstants.METADATA_ENTITY_ATTRIBUTES_NSURI, "EntityAttributes"),
|
||||||
|
|
||||||
UNKNOWN_ELEMENT("");
|
UNKNOWN_ELEMENT("");
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,8 @@ public class IdentityProviderModel implements Serializable {
|
||||||
public static final String LOGIN_HINT = "loginHint";
|
public static final String LOGIN_HINT = "loginHint";
|
||||||
|
|
||||||
public static final String SYNC_MODE = "syncMode";
|
public static final String SYNC_MODE = "syncMode";
|
||||||
|
|
||||||
|
public static final String HIDE_ON_LOGIN = "hideOnLoginPage";
|
||||||
|
|
||||||
private String internalId;
|
private String internalId;
|
||||||
|
|
||||||
|
@ -227,4 +229,13 @@ public class IdentityProviderModel implements Serializable {
|
||||||
public void setLoginHint(boolean loginHint) {
|
public void setLoginHint(boolean loginHint) {
|
||||||
getConfig().put(LOGIN_HINT, String.valueOf(loginHint));
|
getConfig().put(LOGIN_HINT, String.valueOf(loginHint));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isHideOnLogin() {
|
||||||
|
return Boolean.valueOf(getConfig().get(HIDE_ON_LOGIN));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHideOnLogin(boolean hideOnLogin) {
|
||||||
|
getConfig().put(HIDE_ON_LOGIN, String.valueOf(hideOnLogin));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
|
||||||
public static final String WANT_ASSERTIONS_SIGNED = "wantAssertionsSigned";
|
public static final String WANT_ASSERTIONS_SIGNED = "wantAssertionsSigned";
|
||||||
public static final String WANT_AUTHN_REQUESTS_SIGNED = "wantAuthnRequestsSigned";
|
public static final String WANT_AUTHN_REQUESTS_SIGNED = "wantAuthnRequestsSigned";
|
||||||
public static final String XML_SIG_KEY_INFO_KEY_NAME_TRANSFORMER = "xmlSigKeyInfoKeyNameTransformer";
|
public static final String XML_SIG_KEY_INFO_KEY_NAME_TRANSFORMER = "xmlSigKeyInfoKeyNameTransformer";
|
||||||
|
public static final String ENABLED_FROM_METADATA = "enabledFromMetadata";
|
||||||
|
|
||||||
public SAMLIdentityProviderConfig() {
|
public SAMLIdentityProviderConfig() {
|
||||||
}
|
}
|
||||||
|
@ -280,6 +281,14 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
|
||||||
public void setPrincipalAttribute(String principalAttribute) {
|
public void setPrincipalAttribute(String principalAttribute) {
|
||||||
getConfig().put(PRINCIPAL_ATTRIBUTE, principalAttribute);
|
getConfig().put(PRINCIPAL_ATTRIBUTE, principalAttribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEnabledFromMetadata() {
|
||||||
|
return Boolean.valueOf(getConfig().get(ENABLED_FROM_METADATA ));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabledFromMetadata(boolean enabled) {
|
||||||
|
getConfig().put(ENABLED_FROM_METADATA , String.valueOf(enabled));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validate(RealmModel realm) {
|
public void validate(RealmModel realm) {
|
||||||
|
|
|
@ -16,8 +16,18 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.broker.saml;
|
package org.keycloak.broker.saml;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
import org.keycloak.Config.Scope;
|
import org.keycloak.Config.Scope;
|
||||||
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
||||||
import org.keycloak.dom.saml.v2.metadata.EndpointType;
|
import org.keycloak.dom.saml.v2.metadata.EndpointType;
|
||||||
import org.keycloak.dom.saml.v2.metadata.EntitiesDescriptorType;
|
import org.keycloak.dom.saml.v2.metadata.EntitiesDescriptorType;
|
||||||
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
|
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
|
||||||
|
@ -33,12 +43,6 @@ import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
|
||||||
import org.keycloak.saml.validators.DestinationValidator;
|
import org.keycloak.saml.validators.DestinationValidator;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
import javax.xml.namespace.QName;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Pedro Igor
|
* @author Pedro Igor
|
||||||
*/
|
*/
|
||||||
|
@ -46,6 +50,9 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "saml";
|
public static final String PROVIDER_ID = "saml";
|
||||||
|
|
||||||
|
private static final String MACEDIR_ENTITY_CATEGORY = "http://macedir.org/entity-category";
|
||||||
|
private static final String REFEDS_HIDE_FROM_DISCOVERY = "http://refeds.org/category/hide-from-discovery";
|
||||||
|
|
||||||
private DestinationValidator destinationValidator;
|
private DestinationValidator destinationValidator;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -158,6 +165,20 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
samlIdentityProviderConfig.setEnabledFromMetadata(entityType.getValidUntil() == null
|
||||||
|
|| entityType.getValidUntil().toGregorianCalendar().getTime().after(new Date(System.currentTimeMillis())));
|
||||||
|
|
||||||
|
// check for hide on login attribute
|
||||||
|
if (entityType.getExtensions() != null && entityType.getExtensions().getEntityAttributes() != null) {
|
||||||
|
for (AttributeType attribute : entityType.getExtensions().getEntityAttributes().getAttribute()) {
|
||||||
|
if (MACEDIR_ENTITY_CATEGORY.equals(attribute.getName())
|
||||||
|
&& attribute.getAttributeValue().contains(REFEDS_HIDE_FROM_DISCOVERY)) {
|
||||||
|
samlIdentityProviderConfig.setHideOnLogin(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return samlIdentityProviderConfig.getConfig();
|
return samlIdentityProviderConfig.getConfig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,9 @@ package org.keycloak.testsuite.admin;
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
|
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.IdentityProviderResource;
|
import org.keycloak.admin.client.resource.IdentityProviderResource;
|
||||||
|
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
||||||
import org.keycloak.dom.saml.v2.metadata.EndpointType;
|
import org.keycloak.dom.saml.v2.metadata.EndpointType;
|
||||||
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
|
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
|
||||||
import org.keycloak.dom.saml.v2.metadata.IndexedEndpointType;
|
import org.keycloak.dom.saml.v2.metadata.IndexedEndpointType;
|
||||||
|
@ -494,16 +496,16 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private IdentityProviderRepresentation createRep(String id, String providerId) {
|
private IdentityProviderRepresentation createRep(String id, String providerId) {
|
||||||
return createRep(id, providerId, null);
|
return createRep(id, providerId,true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IdentityProviderRepresentation createRep(String id, String providerId, Map<String, String> config) {
|
private IdentityProviderRepresentation createRep(String id, String providerId,boolean enabled, Map<String, String> config) {
|
||||||
IdentityProviderRepresentation idp = new IdentityProviderRepresentation();
|
IdentityProviderRepresentation idp = new IdentityProviderRepresentation();
|
||||||
|
|
||||||
idp.setAlias(id);
|
idp.setAlias(id);
|
||||||
idp.setDisplayName(id);
|
idp.setDisplayName(id);
|
||||||
idp.setProviderId(providerId);
|
idp.setProviderId(providerId);
|
||||||
idp.setEnabled(true);
|
idp.setEnabled(enabled);
|
||||||
if (config != null) {
|
if (config != null) {
|
||||||
idp.setConfig(config);
|
idp.setConfig(config);
|
||||||
}
|
}
|
||||||
|
@ -603,14 +605,14 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
form.addFormData("file", body, MediaType.APPLICATION_XML_TYPE, "saml-idp-metadata.xml");
|
form.addFormData("file", body, MediaType.APPLICATION_XML_TYPE, "saml-idp-metadata.xml");
|
||||||
|
|
||||||
Map<String, String> result = realm.identityProviders().importFrom(form);
|
Map<String, String> result = realm.identityProviders().importFrom(form);
|
||||||
assertSamlImport(result, SIGNING_CERT_1);
|
assertSamlImport(result, SIGNING_CERT_1,true);
|
||||||
|
|
||||||
// Create new SAML identity provider using configuration retrieved from import-config
|
// Create new SAML identity provider using configuration retrieved from import-config
|
||||||
create(createRep("saml", "saml", result));
|
create(createRep("saml", "saml",true, result));
|
||||||
|
|
||||||
IdentityProviderResource provider = realm.identityProviders().get("saml");
|
IdentityProviderResource provider = realm.identityProviders().get("saml");
|
||||||
IdentityProviderRepresentation rep = provider.toRepresentation();
|
IdentityProviderRepresentation rep = provider.toRepresentation();
|
||||||
assertCreatedSamlIdp(rep);
|
assertCreatedSamlIdp(rep,true);
|
||||||
|
|
||||||
// Now list the providers - we should see the one just created
|
// Now list the providers - we should see the one just created
|
||||||
List<IdentityProviderRepresentation> providers = realm.identityProviders().findAll();
|
List<IdentityProviderRepresentation> providers = realm.identityProviders().findAll();
|
||||||
|
@ -626,6 +628,32 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
|
|
||||||
assertSamlExport(body);
|
assertSamlExport(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSamlImportAndExportDisabled() throws URISyntaxException, IOException, ParsingException {
|
||||||
|
|
||||||
|
// Use import-config to convert IDPSSODescriptor file into key value pairs
|
||||||
|
// to use when creating a SAML Identity Provider
|
||||||
|
MultipartFormDataOutput form = new MultipartFormDataOutput();
|
||||||
|
form.addFormData("providerId", "saml", MediaType.TEXT_PLAIN_TYPE);
|
||||||
|
|
||||||
|
URL idpMeta = getClass().getClassLoader().getResource("admin-test/saml-idp-metadata-disabled.xml");
|
||||||
|
byte[] content = Files.readAllBytes(Paths.get(idpMeta.toURI()));
|
||||||
|
String body = new String(content, Charset.forName("utf-8"));
|
||||||
|
form.addFormData("file", body, MediaType.APPLICATION_XML_TYPE, "saml-idp-metadata-disabled.xml");
|
||||||
|
|
||||||
|
Map<String, String> result = realm.identityProviders().importFrom(form);
|
||||||
|
assertSamlImport(result, SIGNING_CERT_1, false);
|
||||||
|
|
||||||
|
// Create new SAML identity provider using configuration retrieved from import-config
|
||||||
|
create(createRep("saml", "saml", false, result));
|
||||||
|
|
||||||
|
IdentityProviderResource provider = realm.identityProviders().get("saml");
|
||||||
|
IdentityProviderRepresentation rep = provider.toRepresentation();
|
||||||
|
assertCreatedSamlIdp(rep, false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSamlImportAndExportMultipleSigningKeys() throws URISyntaxException, IOException, ParsingException {
|
public void testSamlImportAndExportMultipleSigningKeys() throws URISyntaxException, IOException, ParsingException {
|
||||||
|
@ -641,14 +669,14 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
form.addFormData("file", body, MediaType.APPLICATION_XML_TYPE, "saml-idp-metadata-two-signing-certs");
|
form.addFormData("file", body, MediaType.APPLICATION_XML_TYPE, "saml-idp-metadata-two-signing-certs");
|
||||||
|
|
||||||
Map<String, String> result = realm.identityProviders().importFrom(form);
|
Map<String, String> result = realm.identityProviders().importFrom(form);
|
||||||
assertSamlImport(result, SIGNING_CERT_1 + "," + SIGNING_CERT_2);
|
assertSamlImport(result, SIGNING_CERT_1 + "," + SIGNING_CERT_2,true);
|
||||||
|
|
||||||
// Create new SAML identity provider using configuration retrieved from import-config
|
// Create new SAML identity provider using configuration retrieved from import-config
|
||||||
create(createRep("saml", "saml", result));
|
create(createRep("saml", "saml",true, result));
|
||||||
|
|
||||||
IdentityProviderResource provider = realm.identityProviders().get("saml");
|
IdentityProviderResource provider = realm.identityProviders().get("saml");
|
||||||
IdentityProviderRepresentation rep = provider.toRepresentation();
|
IdentityProviderRepresentation rep = provider.toRepresentation();
|
||||||
assertCreatedSamlIdp(rep);
|
assertCreatedSamlIdp(rep,true);
|
||||||
|
|
||||||
// Now list the providers - we should see the one just created
|
// Now list the providers - we should see the one just created
|
||||||
List<IdentityProviderRepresentation> providers = realm.identityProviders().findAll();
|
List<IdentityProviderRepresentation> providers = realm.identityProviders().findAll();
|
||||||
|
@ -863,13 +891,13 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
Assert.assertEquals("config", expected.getConfig(), actual.getConfig());
|
Assert.assertEquals("config", expected.getConfig(), actual.getConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertCreatedSamlIdp(IdentityProviderRepresentation idp) {
|
private void assertCreatedSamlIdp(IdentityProviderRepresentation idp,boolean enabled) {
|
||||||
//System.out.println("idp: " + idp);
|
//System.out.println("idp: " + idp);
|
||||||
Assert.assertNotNull("IdentityProviderRepresentation not null", idp);
|
Assert.assertNotNull("IdentityProviderRepresentation not null", idp);
|
||||||
Assert.assertNotNull("internalId", idp.getInternalId());
|
Assert.assertNotNull("internalId", idp.getInternalId());
|
||||||
Assert.assertEquals("alias", "saml", idp.getAlias());
|
Assert.assertEquals("alias", "saml", idp.getAlias());
|
||||||
Assert.assertEquals("providerId", "saml", idp.getProviderId());
|
Assert.assertEquals("providerId", "saml", idp.getProviderId());
|
||||||
Assert.assertTrue("enabled", idp.isEnabled());
|
Assert.assertEquals("enabled",enabled, idp.isEnabled());
|
||||||
Assert.assertEquals("firstBrokerLoginFlowAlias", "first broker login",idp.getFirstBrokerLoginFlowAlias());
|
Assert.assertEquals("firstBrokerLoginFlowAlias", "first broker login",idp.getFirstBrokerLoginFlowAlias());
|
||||||
assertSamlConfig(idp.getConfig());
|
assertSamlConfig(idp.getConfig());
|
||||||
}
|
}
|
||||||
|
@ -889,7 +917,8 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
"nameIDPolicyFormat",
|
"nameIDPolicyFormat",
|
||||||
"signingCertificate",
|
"signingCertificate",
|
||||||
"addExtensionsElementWithKeyInfo",
|
"addExtensionsElementWithKeyInfo",
|
||||||
"loginHint"
|
"loginHint",
|
||||||
|
"hideOnLoginPage"
|
||||||
));
|
));
|
||||||
assertThat(config, hasEntry("validateSignature", "true"));
|
assertThat(config, hasEntry("validateSignature", "true"));
|
||||||
assertThat(config, hasEntry("singleLogoutServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml"));
|
assertThat(config, hasEntry("singleLogoutServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml"));
|
||||||
|
@ -899,10 +928,15 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
assertThat(config, hasEntry("wantAuthnRequestsSigned", "true"));
|
assertThat(config, hasEntry("wantAuthnRequestsSigned", "true"));
|
||||||
assertThat(config, hasEntry("addExtensionsElementWithKeyInfo", "false"));
|
assertThat(config, hasEntry("addExtensionsElementWithKeyInfo", "false"));
|
||||||
assertThat(config, hasEntry("nameIDPolicyFormat", "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"));
|
assertThat(config, hasEntry("nameIDPolicyFormat", "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"));
|
||||||
|
assertThat(config, hasEntry("hideOnLoginPage", "true"));
|
||||||
assertThat(config, hasEntry(is("signingCertificate"), notNullValue()));
|
assertThat(config, hasEntry(is("signingCertificate"), notNullValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertSamlImport(Map<String, String> config, String expectedSigningCertificates) {
|
private void assertSamlImport(Map<String, String> config, String expectedSigningCertificates,boolean enabled) {
|
||||||
|
//firtsly check and remove enabledFromMetadata from config
|
||||||
|
boolean enabledFromMetadata = Boolean.valueOf(config.get(SAMLIdentityProviderConfig.ENABLED_FROM_METADATA));
|
||||||
|
config.remove(SAMLIdentityProviderConfig.ENABLED_FROM_METADATA);
|
||||||
|
Assert.assertEquals(enabledFromMetadata,enabled);
|
||||||
assertSamlConfig(config);
|
assertSamlConfig(config);
|
||||||
assertThat(config, hasEntry("signingCertificate", expectedSigningCertificates));
|
assertThat(config, hasEntry("signingCertificate", expectedSigningCertificates));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<EntityDescriptor entityID="http://localhost:8080/auth/realms/master"
|
||||||
|
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
|
xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
|
||||||
|
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
||||||
|
xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
|
||||||
|
validUntil="2019-12-10T04:48:55Z"
|
||||||
|
>
|
||||||
|
<Extensions>
|
||||||
|
<mdattr:EntityAttributes>
|
||||||
|
<saml:Attribute Name="http://macedir.org/entity-category" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
||||||
|
<saml:AttributeValue>http://refeds.org/category/hide-from-discovery</saml:AttributeValue>
|
||||||
|
</saml:Attribute>
|
||||||
|
</mdattr:EntityAttributes>
|
||||||
|
</Extensions>
|
||||||
|
<IDPSSODescriptor WantAuthnRequestsSigned="true"
|
||||||
|
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||||
|
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
|
||||||
|
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
|
||||||
|
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
|
||||||
|
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
|
||||||
|
|
||||||
|
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||||
|
Location="http://localhost:8080/auth/realms/master/protocol/saml" />
|
||||||
|
<SingleLogoutService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||||
|
Location="http://localhost:8080/auth/realms/master/protocol/saml" />
|
||||||
|
<KeyDescriptor use="signing">
|
||||||
|
<dsig:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<dsig:X509Data>
|
||||||
|
<dsig:X509Certificate>
|
||||||
|
MIICmzCCAYMCBgFUYnC0OjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMTYwNDI5MTQzMjEzWhcNMjYwNDI5MTQzMzUzWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCN25AW1poMEZRbuMAHG58AThZmCwMV6/Gcui4mjGacRFyudgqzLjQ2rxpoW41JAtLjbjeAhuWvirUcFVcOeS3gM/ZC27qCpYighAcylZz6MYocnEe1+e8rPPk4JlID6Wv62dgu+pL/vYsQpRhvD3Y2c/ytgr5D32xF+KnzDehUy5BSyzypvu12Wq9mS5vK5tzkN37EjkhpY2ZxaXPubjDIITCAL4Q8M/m5IlacBaUZbzI4AQrHnMP1O1IH2dHSWuMiBe+xSDTco72PmuYPJKTV4wQdeBUIkYbfLc4RxVmXEvgkQgyW86EoMPxlWJpj7+mTIR+l+2thZPr/VgwTs82rAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAA/Ip/Hi8RoVu5ouaFFlc5whT7ltuK8slfLGW4tM4vJXhInYwsqIRQKBNDYW/64xle3eII4u1yAH1OYRRwEs7Em1pr4QuFuTY1at+aE0sE46XDlyESI0txJjWxYoT133vM0We2pj1b2nxgU30rwjKA3whnKEfTEYT/n3JBSqNggy6l8ZGw/oPSgvPaR4+xeB1tfQFC4VrLoYKoqH6hAL530nKxL+qV8AIfL64NDEE8ankIAEDAAFe8x3CPUfXR/p4KOANKkpz8ieQaHDb1eITkAwUwjESj6UF9D1aePlhWls/HX0gujFXtWfWfrJ8CU/ogwlH8y1jgRuLjFQYZk6llc=
|
||||||
|
</dsig:X509Certificate>
|
||||||
|
</dsig:X509Data>
|
||||||
|
</dsig:KeyInfo>
|
||||||
|
</KeyDescriptor>
|
||||||
|
</IDPSSODescriptor>
|
||||||
|
</EntityDescriptor>
|
|
@ -1,8 +1,17 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<EntityDescriptor entityID="http://localhost:8080/auth/realms/master"
|
<EntityDescriptor entityID="http://localhost:8080/auth/realms/master"
|
||||||
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
|
xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
|
||||||
|
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
||||||
xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
|
xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
|
||||||
>
|
>
|
||||||
|
<Extensions>
|
||||||
|
<mdattr:EntityAttributes>
|
||||||
|
<saml:Attribute Name="http://macedir.org/entity-category" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
||||||
|
<saml:AttributeValue>http://refeds.org/category/hide-from-discovery</saml:AttributeValue>
|
||||||
|
</saml:Attribute>
|
||||||
|
</mdattr:EntityAttributes>
|
||||||
|
</Extensions>
|
||||||
<IDPSSODescriptor WantAuthnRequestsSigned="true"
|
<IDPSSODescriptor WantAuthnRequestsSigned="true"
|
||||||
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<EntityDescriptor entityID="http://localhost:8080/auth/realms/master"
|
<EntityDescriptor entityID="http://localhost:8080/auth/realms/master"
|
||||||
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
|
xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
|
||||||
|
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
||||||
xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
|
xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
|
||||||
>
|
>
|
||||||
|
<Extensions>
|
||||||
|
<mdattr:EntityAttributes>
|
||||||
|
<saml:Attribute Name="http://macedir.org/entity-category" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
||||||
|
<saml:AttributeValue>http://refeds.org/category/hide-from-discovery</saml:AttributeValue>
|
||||||
|
</saml:Attribute>
|
||||||
|
</mdattr:EntityAttributes>
|
||||||
|
</Extensions>
|
||||||
<IDPSSODescriptor WantAuthnRequestsSigned="true"
|
<IDPSSODescriptor WantAuthnRequestsSigned="true"
|
||||||
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||||
<KeyDescriptor use="signing">
|
<KeyDescriptor use="signing">
|
||||||
|
|
|
@ -962,9 +962,14 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
|
||||||
};
|
};
|
||||||
|
|
||||||
var setConfig = function(data) {
|
var setConfig = function(data) {
|
||||||
|
if (data["enabledFromMetadata"] !== undefined ) {
|
||||||
|
$scope.identityProvider.enabled = data["enabledFromMetadata"] == "true";
|
||||||
|
delete data["enabledFromMetadata"];
|
||||||
|
}
|
||||||
for (var key in data) {
|
for (var key in data) {
|
||||||
$scope.identityProvider.config[key] = data[key];
|
$scope.identityProvider.config[key] = data[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.uploadFile = function() {
|
$scope.uploadFile = function() {
|
||||||
|
|
Loading…
Reference in a new issue