diff --git a/docker/keycloak/extensions-24/services/src/main/java/com/github/bcgov/keycloak/broker/saml/OverrideSAMLIdentityProviderFactory.java b/docker/keycloak/extensions-24/services/src/main/java/com/github/bcgov/keycloak/broker/saml/OverrideSAMLIdentityProviderFactory.java index f11c3dfc..2d7db995 100644 --- a/docker/keycloak/extensions-24/services/src/main/java/com/github/bcgov/keycloak/broker/saml/OverrideSAMLIdentityProviderFactory.java +++ b/docker/keycloak/extensions-24/services/src/main/java/com/github/bcgov/keycloak/broker/saml/OverrideSAMLIdentityProviderFactory.java @@ -2,12 +2,38 @@ import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; + +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.broker.provider.AbstractIdentityProviderFactory; import org.keycloak.broker.saml.SAMLIdentityProviderConfig; -import org.keycloak.broker.saml.SAMLIdentityProviderFactory; +import org.keycloak.dom.saml.v2.assertion.AttributeType; +import org.keycloak.dom.saml.v2.metadata.EndpointType; +import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType; +import org.keycloak.dom.saml.v2.metadata.IDPSSODescriptorType; +import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType; +import org.keycloak.dom.saml.v2.metadata.KeyTypes; +import org.keycloak.saml.common.constants.JBossSAMLURIConstants; +import org.keycloak.saml.common.exceptions.ParsingException; +import org.keycloak.saml.common.util.DocumentUtil; +import org.keycloak.saml.processing.core.saml.v2.util.SAMLMetadataUtil; import org.keycloak.saml.validators.DestinationValidator; +import org.w3c.dom.Element; + +public class OverrideSAMLIdentityProviderFactory extends AbstractIdentityProviderFactory { -public class OverrideSAMLIdentityProviderFactory extends SAMLIdentityProviderFactory { + public static final String PROVIDER_ID = "custom-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; @@ -21,9 +47,121 @@ public String getName() { return "SAML v2.0 - Custom"; } + @Override + public String getId() { + return PROVIDER_ID; + } + @Override public void init(Scope config) { super.init(config); this.destinationValidator = DestinationValidator.forProtocolMap(config.getArray("knownProtocols")); } + + @Override + public SAMLIdentityProviderConfig createConfig() { + return new SAMLIdentityProviderConfig(); + } + + @Override + public Map parseConfig(KeycloakSession session, InputStream inputStream) { + try { + EntityDescriptorType entityType = SAMLMetadataUtil.parseEntityDescriptorType(inputStream); + IDPSSODescriptorType idpDescriptor = SAMLMetadataUtil.locateIDPSSODescriptorType(entityType); + + if (idpDescriptor != null) { + SAMLIdentityProviderConfig samlIdentityProviderConfig = new SAMLIdentityProviderConfig(); + String singleSignOnServiceUrl = null; + boolean postBindingResponse = false; + boolean postBindingLogout = false; + for (EndpointType endpoint : idpDescriptor.getSingleSignOnService()) { + if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) { + singleSignOnServiceUrl = endpoint.getLocation().toString(); + postBindingResponse = true; + break; + } else if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())) { + singleSignOnServiceUrl = endpoint.getLocation().toString(); + } + } + String singleLogoutServiceUrl = null; + for (EndpointType endpoint : idpDescriptor.getSingleLogoutService()) { + if (postBindingResponse + && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) { + singleLogoutServiceUrl = endpoint.getLocation().toString(); + postBindingLogout = true; + break; + } else if (!postBindingResponse + && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())) { + singleLogoutServiceUrl = endpoint.getLocation().toString(); + break; + } + + } + samlIdentityProviderConfig.setIdpEntityId(entityType.getEntityID()); + samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl); + samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl); + samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned()); + samlIdentityProviderConfig.setAddExtensionsElementWithKeyInfo(false); + samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned()); + samlIdentityProviderConfig.setPostBindingResponse(postBindingResponse); + samlIdentityProviderConfig.setPostBindingAuthnRequest(postBindingResponse); + samlIdentityProviderConfig.setPostBindingLogout(postBindingLogout); + samlIdentityProviderConfig.setLoginHint(false); + + List nameIdFormatList = idpDescriptor.getNameIDFormat(); + if (nameIdFormatList != null && !nameIdFormatList.isEmpty()) { + samlIdentityProviderConfig.setNameIDPolicyFormat(nameIdFormatList.get(0)); + } + + List keyDescriptor = idpDescriptor.getKeyDescriptor(); + String defaultCertificate = null; + + if (keyDescriptor != null) { + for (KeyDescriptorType keyDescriptorType : keyDescriptor) { + Element keyInfo = keyDescriptorType.getKeyInfo(); + Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate")); + + if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) { + samlIdentityProviderConfig.addSigningCertificate(x509KeyInfo.getTextContent()); + } else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) { + samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent()); + } else if (keyDescriptorType.getUse() == null) { + defaultCertificate = x509KeyInfo.getTextContent(); + } + } + } + + if (defaultCertificate != null) { + if (samlIdentityProviderConfig.getSigningCertificates().length == 0) { + samlIdentityProviderConfig.addSigningCertificate(defaultCertificate); + } + + if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) { + samlIdentityProviderConfig.setEncryptionPublicKey(defaultCertificate); + } + } + + 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(); + } + } catch (ParsingException pe) { + throw new RuntimeException("Could not parse IdP SAML Metadata", pe); + } + + return new HashMap<>(); + } + }