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 9e70fc21..b1ec4409 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,21 +2,38 @@ import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; -import org.keycloak.broker.provider.AbstractIdentityProviderFactory; -import org.keycloak.broker.saml.SAMLIdentityProvider; 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; 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; -public class OverrideSAMLIdentityProviderFactory extends AbstractIdentityProviderFactory { +public class OverrideSAMLIdentityProviderFactory extends SAMLIdentityProviderFactory { public static final String PROVIDER_ID = "saml-custom"; + 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; @Override @@ -45,7 +62,104 @@ public SAMLIdentityProviderConfig createConfig() { return new SAMLIdentityProviderConfig(); } + @Override public Map parseConfig(KeycloakSession session, InputStream inputStream) { - return new SAMLIdentityProviderFactory().parseConfig(session, 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<>(); } }