From 4c9652abc3a4c328cc7d46f1031d580092a7be7a Mon Sep 17 00:00:00 2001 From: Aldo Mihasi Date: Mon, 12 Jun 2023 15:05:51 +0300 Subject: [PATCH] saml2 metadata creation for sp --- .../controllers/Saml2MetadataController.java | 59 ++++++++ .../entities/saml2/CertificateInfo.java | 68 +++++++++ .../saml2/Saml2ConfigurableProvider.java | 111 ++++---------- .../configurableProvider/Saml2SSOUtils.java | 135 ++++++++++++++++-- .../resources/configurableLoginProviders.json | 37 +++++ 5 files changed, 311 insertions(+), 99 deletions(-) create mode 100644 dmp-backend/web/src/main/java/eu/eudat/controllers/Saml2MetadataController.java create mode 100644 dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/ConfigurableProvider/entities/saml2/CertificateInfo.java diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/Saml2MetadataController.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/Saml2MetadataController.java new file mode 100644 index 000000000..0acfe4ac5 --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/Saml2MetadataController.java @@ -0,0 +1,59 @@ +package eu.eudat.controllers; + +import eu.eudat.logic.proxy.config.configloaders.ConfigLoader; +import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider; +import eu.eudat.logic.security.validators.configurableProvider.Saml2SSOUtils; +import eu.eudat.logic.services.ApiContext; +import eu.eudat.models.data.helpers.responses.ResponseItem; +import eu.eudat.models.data.security.Principal; +import eu.eudat.types.ApiMessageCode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.nio.charset.StandardCharsets; + +@RestController +@CrossOrigin +@RequestMapping(value = {"/api/saml2/metadata"}) +public class Saml2MetadataController extends BaseController { + + private final ConfigLoader configLoader; + + @Autowired + public Saml2MetadataController(ApiContext apiContext, ConfigLoader configLoader) { + super(apiContext); + this.configLoader = configLoader; + } + + @RequestMapping(method = RequestMethod.GET, value = {"/{configurableProviderId}"}) + public @ResponseBody + ResponseEntity getMetadata(@PathVariable String configurableProviderId) { + Saml2ConfigurableProvider saml2ConfigurableProvider = (Saml2ConfigurableProvider) this.configLoader.getConfigurableProviders().getProviders().stream() + .filter(prov -> prov.getConfigurableLoginId().equals(configurableProviderId)) + .findFirst().orElse(null); + if (saml2ConfigurableProvider != null) { + try { + String metadataXml = Saml2SSOUtils.getMetadata(saml2ConfigurableProvider); + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.setContentLength(metadataXml.length()); + responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); + responseHeaders.set("Content-Disposition", "attachment;filename=" + configurableProviderId + ".xml"); + responseHeaders.set("Access-Control-Expose-Headers", "Content-Disposition"); + responseHeaders.get("Access-Control-Expose-Headers").add("Content-Type"); + return new ResponseEntity<>(metadataXml.getBytes(StandardCharsets.UTF_8), + responseHeaders, + HttpStatus.OK); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseItem().status(ApiMessageCode.ERROR_MESSAGE).message("Failed to fetch metadata.")); + } + } + else { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseItem().status(ApiMessageCode.ERROR_MESSAGE).message("Failed to fetch metadata.")); + } + } + +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/ConfigurableProvider/entities/saml2/CertificateInfo.java b/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/ConfigurableProvider/entities/saml2/CertificateInfo.java new file mode 100644 index 000000000..fbbd333d3 --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/ConfigurableProvider/entities/saml2/CertificateInfo.java @@ -0,0 +1,68 @@ +package eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2; + +import com.fasterxml.jackson.annotation.JsonValue; + +public class CertificateInfo { + + public enum KeyFormat { + JKS("JKS"), PKCS12("PKCS12"); + + private String type; + KeyFormat(String type) { + this.type = type; + } + @JsonValue + public String getType() { return type; } + + public static KeyFormat fromType(String type) { + for (KeyFormat t: KeyFormat.values()) { + if (type.equals(t.getType())) { + return t; + } + } + throw new IllegalArgumentException("Unsupported Keystore format " + type); + } + } + + private String alias; + private String password; + private String keystorePath; + private String keystorePassword; + private KeyFormat keyFormat; + + public String getAlias() { + return alias; + } + public void setAlias(String alias) { + this.alias = alias; + } + + public String getPassword() { + return password; + } + public void setPassword(String password) { + this.password = password; + } + + public String getKeystorePath() { + return keystorePath; + } + public void setKeystorePath(String keystorePath) { + this.keystorePath = keystorePath; + } + + public String getKeystorePassword() { + return keystorePassword; + } + public void setKeystorePassword(String keystorePassword) { + this.keystorePassword = keystorePassword; + } + + public KeyFormat getKeyFormat() { + return keyFormat; + } + public void setKeyFormat(KeyFormat keyFormat) { + this.keyFormat = keyFormat; + } + +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/ConfigurableProvider/entities/saml2/Saml2ConfigurableProvider.java b/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/ConfigurableProvider/entities/saml2/Saml2ConfigurableProvider.java index ed3ddfbf7..3280e1575 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/ConfigurableProvider/entities/saml2/Saml2ConfigurableProvider.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/ConfigurableProvider/entities/saml2/Saml2ConfigurableProvider.java @@ -47,49 +47,24 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider { } } - public enum KeyFormat { - JKS("JKS"), PKCS12("PKCS12"); - - private String type; - KeyFormat(String type) { - this.type = type; - } - @JsonValue - public String getType() { return type; } - - public static KeyFormat fromType(String type) { - for (KeyFormat t: KeyFormat.values()) { - if (type.equals(t.getType())) { - return t; - } - } - throw new IllegalArgumentException("Unsupported Keystore format " + type); - } - } - private String spEntityId; private String idpEntityId; private String idpUrl; private String idpArtifactUrl; private String idpMetadataUrl; private boolean assertionEncrypted; - private KeyFormat keyFormat; - private String keyAlias; - private String credentialPath; - private String archivePassword; - private String keyPassword; + private CertificateInfo encryptionCert; + private CertificateInfo signingCert; private boolean responseSigned; private boolean assertionSigned; private boolean signatureRequired; - private String signatureKeyAlias; - private String signaturePath; - private String signatureKeyStorePassword; - private String signatureKeyPassword; private SAML2UsingFormat usingFormat; private Map attributeTypes; private Map configurableUserFromAttributes; private String binding; private String assertionConsumerServiceUrl; + private boolean wantAssertionsSigned; + private boolean authnRequestsSigned; public String getSpEntityId() { return spEntityId; @@ -133,39 +108,18 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider { this.assertionEncrypted = assertionEncrypted; } - public KeyFormat getKeyFormat() { - return keyFormat; + public CertificateInfo getEncryptionCert() { + return encryptionCert; } - public void setKeyFormat(KeyFormat keyFormat) { - this.keyFormat = keyFormat; + public void setEncryptionCert(CertificateInfo encryptionCert) { + this.encryptionCert = encryptionCert; } - public String getKeyAlias() { - return keyAlias; + public CertificateInfo getSigningCert() { + return signingCert; } - public void setKeyAlias(String keyAlias) { - this.keyAlias = keyAlias; - } - - public String getCredentialPath() { - return credentialPath; - } - public void setCredentialPath(String credentialPath) { - this.credentialPath = credentialPath; - } - - public String getArchivePassword() { - return archivePassword; - } - public void setArchivePassword(String archivePassword) { - this.archivePassword = archivePassword; - } - - public String getKeyPassword() { - return keyPassword; - } - public void setKeyPassword(String keyPassword) { - this.keyPassword = keyPassword; + public void setSigningCert(CertificateInfo signingCert) { + this.signingCert = signingCert; } public boolean isResponseSigned() { @@ -189,34 +143,6 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider { this.signatureRequired = signatureRequired; } - public String getSignatureKeyAlias() { - return signatureKeyAlias; - } - public void setSignatureKeyAlias(String signatureKeyAlias) { - this.signatureKeyAlias = signatureKeyAlias; - } - - public String getSignaturePath() { - return signaturePath; - } - public void setSignaturePath(String signaturePath) { - this.signaturePath = signaturePath; - } - - public String getSignatureKeyStorePassword() { - return signatureKeyStorePassword; - } - public void setSignatureKeyStorePassword(String signatureKeyStorePassword) { - this.signatureKeyStorePassword = signatureKeyStorePassword; - } - - public String getSignatureKeyPassword() { - return signatureKeyPassword; - } - public void setSignatureKeyPassword(String signatureKeyPassword) { - this.signatureKeyPassword = signatureKeyPassword; - } - public SAML2UsingFormat getUsingFormat() { return usingFormat; } @@ -252,4 +178,17 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider { this.assertionConsumerServiceUrl = assertionConsumerServiceUrl; } + public boolean isWantAssertionsSigned() { + return wantAssertionsSigned; + } + public void setWantAssertionsSigned(boolean wantAssertionsSigned) { + this.wantAssertionsSigned = wantAssertionsSigned; + } + + public boolean isAuthnRequestsSigned() { + return authnRequestsSigned; + } + public void setAuthnRequestsSigned(boolean authnRequestsSigned) { + this.authnRequestsSigned = authnRequestsSigned; + } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/configurableProvider/Saml2SSOUtils.java b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/configurableProvider/Saml2SSOUtils.java index b6f413dc6..dd6b77294 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/configurableProvider/Saml2SSOUtils.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/configurableProvider/Saml2SSOUtils.java @@ -1,6 +1,8 @@ package eu.eudat.logic.security.validators.configurableProvider; +import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.CertificateInfo; import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider; +import eu.eudat.logic.utilities.builders.XmlBuilder; import jakarta.xml.soap.*; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.resolver.CriteriaSet; @@ -32,6 +34,7 @@ import org.opensaml.core.xml.config.XMLObjectProviderRegistry; import org.opensaml.core.xml.io.*; import org.opensaml.core.xml.schema.*; import org.opensaml.saml.common.SAMLObject; +import org.opensaml.saml.common.SAMLObjectContentReference; import org.opensaml.saml.common.SAMLVersion; import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.criterion.EntityRoleCriterion; @@ -40,7 +43,7 @@ import org.opensaml.saml.metadata.resolver.impl.HTTPMetadataResolver; import org.opensaml.saml.metadata.resolver.impl.PredicateRoleDescriptorResolver; import org.opensaml.saml.saml2.core.*; import org.opensaml.saml.saml2.encryption.Decrypter; -import org.opensaml.saml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.saml.saml2.metadata.*; import org.opensaml.saml.security.impl.MetadataCredentialResolver; import org.opensaml.security.credential.Credential; import org.opensaml.security.credential.CredentialSupport; @@ -55,10 +58,14 @@ import org.opensaml.xml.util.Base64; import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; import org.opensaml.xmlsec.encryption.EncryptedKey; import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; +import org.opensaml.xmlsec.keyinfo.KeyInfoGenerator; import org.opensaml.xmlsec.keyinfo.impl.StaticKeyInfoCredentialResolver; +import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; import org.opensaml.xmlsec.signature.KeyInfo; import org.opensaml.xmlsec.signature.Signature; import org.opensaml.xmlsec.signature.X509Data; +import org.opensaml.xmlsec.signature.impl.SignatureBuilder; +import org.opensaml.xmlsec.signature.support.SignatureConstants; import org.opensaml.xmlsec.signature.support.SignatureValidator; import org.opensaml.xmlsec.signature.support.Signer; import org.slf4j.Logger; @@ -85,6 +92,7 @@ import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.Instant; import java.util.*; @@ -264,7 +272,7 @@ public class Saml2SSOUtils { ArtifactResolve artifactResolve = createArtifactResolveObject(samlArtReceived, saml2Provider.getSpEntityId()); if (saml2Provider.isSignatureRequired()) { - signArtifactResolveReq(artifactResolve, saml2Provider); + signArtifactResolveReq(artifactResolve, saml2Provider.getSigningCert()); } return artifactResolve; @@ -290,14 +298,14 @@ public class Saml2SSOUtils { } - private static void signArtifactResolveReq(ArtifactResolve artifactResolve, Saml2ConfigurableProvider saml2Provider) throws Exception { + private static void signArtifactResolveReq(ArtifactResolve artifactResolve, CertificateInfo singingCertificateInfo) throws Exception { try { KeyStore ks = KeyStore.getInstance("JKS"); - String archivePassword = saml2Provider.getSignatureKeyStorePassword(); + String archivePassword = singingCertificateInfo.getKeystorePassword(); char[] pwdArray = (archivePassword != null) ? archivePassword.toCharArray() : "changeit".toCharArray(); - ks.load(new FileInputStream(saml2Provider.getSignaturePath()), pwdArray); - X509Credential cred = new KeyStoreX509CredentialAdapter(ks, saml2Provider.getSignatureKeyAlias(), saml2Provider.getSignatureKeyPassword().toCharArray()); + ks.load(new FileInputStream(singingCertificateInfo.getKeystorePath()), pwdArray); + X509Credential cred = new KeyStoreX509CredentialAdapter(ks, singingCertificateInfo.getAlias(), singingCertificateInfo.getPassword().toCharArray()); Signature signature = setSignatureRaw(XMLSignature.ALGO_ID_SIGNATURE_RSA, cred); artifactResolve.setSignature(signature); @@ -541,7 +549,7 @@ public class Saml2SSOUtils { if (!CollectionUtils.isEmpty(encryptedAssertions)) { encryptedAssertion = encryptedAssertions.get(0); try { - assertion = getDecryptedAssertion(encryptedAssertion, saml2Provider); + assertion = getDecryptedAssertion(encryptedAssertion, saml2Provider.getEncryptionCert()); } catch (Exception e) { throw new Exception("Unable to decrypt the SAML2 Assertion"); } @@ -601,15 +609,15 @@ public class Saml2SSOUtils { } - private static Assertion getDecryptedAssertion(EncryptedAssertion encryptedAssertion, Saml2ConfigurableProvider saml2Provider) throws Exception { + private static Assertion getDecryptedAssertion(EncryptedAssertion encryptedAssertion, CertificateInfo encryptionCertificateInfo) throws Exception { try { - KeyStore ks = (saml2Provider.getKeyFormat().getType().equals("JKS")) ? KeyStore.getInstance("JKS") : KeyStore.getInstance("PKCS12"); - String archivePassword = saml2Provider.getArchivePassword(); + KeyStore ks = (encryptionCertificateInfo.getKeyFormat().getType().equals("JKS")) ? KeyStore.getInstance("JKS") : KeyStore.getInstance("PKCS12"); + String archivePassword = encryptionCertificateInfo.getKeystorePassword(); char[] pwdArray = (archivePassword != null) ? archivePassword.toCharArray() : "changeit".toCharArray(); - ks.load(new FileInputStream(saml2Provider.getCredentialPath()), pwdArray); - X509Certificate cert = (X509Certificate)ks.getCertificate(saml2Provider.getKeyAlias()); - PrivateKey pk = (PrivateKey) ks.getKey(saml2Provider.getKeyAlias(), saml2Provider.getKeyPassword().toCharArray()); + ks.load(new FileInputStream(encryptionCertificateInfo.getKeystorePath()), pwdArray); + X509Certificate cert = (X509Certificate)ks.getCertificate(encryptionCertificateInfo.getAlias()); + PrivateKey pk = (PrivateKey) ks.getKey(encryptionCertificateInfo.getAlias(), encryptionCertificateInfo.getPassword().toCharArray()); KeyInfoCredentialResolver keyResolver = new StaticKeyInfoCredentialResolver( new BasicX509Credential(cert, pk)); EncryptedKey key = encryptedAssertion.getEncryptedData().getKeyInfo().getEncryptedKeys().get(0); @@ -685,4 +693,105 @@ public class Saml2SSOUtils { } } + private static Credential getCredential(CertificateInfo certificateInfo) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException { + KeyStore ks = (certificateInfo.getKeyFormat().getType().equals("JKS")) ? KeyStore.getInstance("JKS") : KeyStore.getInstance("PKCS12"); + String archivePassword = certificateInfo.getKeystorePassword(); + char[] pwdArray = (archivePassword != null) ? archivePassword.toCharArray() : "changeit".toCharArray(); + ks.load(new FileInputStream(certificateInfo.getKeystorePath()), pwdArray); + X509Certificate cert = (X509Certificate)ks.getCertificate(certificateInfo.getAlias()); + PrivateKey pk = (PrivateKey) ks.getKey(certificateInfo.getAlias(), certificateInfo.getPassword().toCharArray()); + return new BasicX509Credential(cert, pk); + } + + public static String getMetadata(Saml2ConfigurableProvider provider) throws Exception { + + EntityDescriptor spEntityDescriptor = (EntityDescriptor) buildXMLObject(EntityDescriptor.DEFAULT_ELEMENT_NAME); + spEntityDescriptor.setEntityID(provider.getSpEntityId()); + SPSSODescriptor spSSODescriptor = (SPSSODescriptor) buildXMLObject(SPSSODescriptor.DEFAULT_ELEMENT_NAME); + + spSSODescriptor.setWantAssertionsSigned(provider.isWantAssertionsSigned()); spSSODescriptor.setAuthnRequestsSigned(provider.isAuthnRequestsSigned()); + + X509KeyInfoGeneratorFactory keyInfoGeneratorFactory = new X509KeyInfoGeneratorFactory(); + keyInfoGeneratorFactory.setEmitEntityCertificate(true); + KeyInfoGenerator keyInfoGenerator = keyInfoGeneratorFactory.newInstance(); + + if (provider.isAssertionEncrypted()) { + + KeyDescriptor encKeyDescriptor = (KeyDescriptor) buildXMLObject(KeyDescriptor.DEFAULT_ELEMENT_NAME); + + encKeyDescriptor.setUse(UsageType.ENCRYPTION); //Set usage + + // Generating key info. The element will contain the public key. The key is used to by the IDP to encrypt data + try { + encKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(getCredential(provider.getEncryptionCert()))); + } catch (SecurityException e) { + logger.error(e.getMessage(), e); + } + + spSSODescriptor.getKeyDescriptors().add(encKeyDescriptor); + + } + + if (provider.isWantAssertionsSigned()) { + + KeyDescriptor signKeyDescriptor = (KeyDescriptor) buildXMLObject(KeyDescriptor.DEFAULT_ELEMENT_NAME); + + signKeyDescriptor.setUse(UsageType.SIGNING); //Set usage + + // Generating key info. The element will contain the public key. The key is used to by the IDP to verify signatures + try { + signKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(getCredential(provider.getSigningCert()))); + } catch (SecurityException e) { + logger.error(e.getMessage(), e); + } + + spSSODescriptor.getKeyDescriptors().add(signKeyDescriptor); + + } + + NameIDFormat nameIDFormat = (NameIDFormat) buildXMLObject(NameIDFormat.DEFAULT_ELEMENT_NAME); + nameIDFormat.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:transient"); + spSSODescriptor.getNameIDFormats().add(nameIDFormat); + + + AssertionConsumerService assertionConsumerService = (AssertionConsumerService) buildXMLObject(AssertionConsumerService.DEFAULT_ELEMENT_NAME); + assertionConsumerService.setIndex(0); + switch (provider.getBinding()) { + case "Redirect": + assertionConsumerService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); + break; + case "Artifact": + assertionConsumerService.setBinding(SAMLConstants.SAML2_ARTIFACT_BINDING_URI); + break; + case "Post": + assertionConsumerService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); + break; + } + + assertionConsumerService.setLocation(provider.getAssertionConsumerServiceUrl()); + spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService); + + + spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); + + spEntityDescriptor.getRoleDescriptors().add(spSSODescriptor); + + + String metadataXML = null; + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + Marshaller out = registry.getMarshallerFactory().getMarshaller(spEntityDescriptor); + out.marshall(spEntityDescriptor, document); + + metadataXML = XmlBuilder.generateXml(document); + } + catch (MarshallingException | ParserConfigurationException e) { + logger.error(e.getMessage(), e); + } + return metadataXML; + + } + } \ No newline at end of file diff --git a/dmp-backend/web/src/main/resources/configurableLoginProviders.json b/dmp-backend/web/src/main/resources/configurableLoginProviders.json index c569f0dd6..b2cd6b9ee 100644 --- a/dmp-backend/web/src/main/resources/configurableLoginProviders.json +++ b/dmp-backend/web/src/main/resources/configurableLoginProviders.json @@ -30,6 +30,43 @@ "type": "saml2", "configurableLoginId": "", "name": "" + "name": "", + "spEntityId": "", + "idpEntityId": "", + "idpUrl": "", + "idpMetadataUrl": "", + "idpArtifactUrl": "", + "binding": "Redirect", + "logoUrl": "", + "responseSigned": true, + "assertionSigned": true, + "assertionEncrypted": true, + "encryptionCert" : { + "alias": "", + "password": "", + "keystorePath": "encryptionkeystore.jks", + "keystorePassword": "", + "keyFormat": "JKS" + }, + "signingCert" : { + "alias": "", + "password": "", + "keystorePath": "signingkeystore.jks", + "keystorePassword": "", + "keyFormat": "JKS" + }, + "assertionConsumerServiceUrl": "", + "wantAssertionsSigned": true, + "authnRequestsSigned": true, + "usingFormat": "friendly_name", + "configurableUserFromAttributes": { + "email": "email", + "name": "givenName" + }, + "attributeTypes": { + "email": "XSString", + "givenName": "XSString" + } } ] }