diff --git a/Backend/alfresco/module/src/main/java/org/edu_sharing/alfresco/policy/NodeCustomizationPolicies.java b/Backend/alfresco/module/src/main/java/org/edu_sharing/alfresco/policy/NodeCustomizationPolicies.java index 851f0540a..14edea74b 100644 --- a/Backend/alfresco/module/src/main/java/org/edu_sharing/alfresco/policy/NodeCustomizationPolicies.java +++ b/Backend/alfresco/module/src/main/java/org/edu_sharing/alfresco/policy/NodeCustomizationPolicies.java @@ -252,7 +252,9 @@ public void onCopyComplete(QName classRef, Map copyMap) { // add/override the previous node context copyMap.forEach((source,dest)-> { - addCurrentContext(dest); + if(nodeService.exists(dest)) { + addCurrentContext(dest); + } }); } diff --git a/Backend/alfresco/module/src/main/java/org/edu_sharing/alfresco/policy/OnCopyTinymcePolicy.java b/Backend/alfresco/module/src/main/java/org/edu_sharing/alfresco/policy/OnCopyTinymcePolicy.java index 029439278..5828ffe28 100644 --- a/Backend/alfresco/module/src/main/java/org/edu_sharing/alfresco/policy/OnCopyTinymcePolicy.java +++ b/Backend/alfresco/module/src/main/java/org/edu_sharing/alfresco/policy/OnCopyTinymcePolicy.java @@ -38,10 +38,13 @@ public void onCopyComplete(QName classRef, NodeRef sourceNodeRef, NodeRef target Map copyMap) { for (Map.Entry copyMapEntry : copyMap.entrySet()) { - String editoryType = (String) nodeService.getProperty(copyMapEntry.getValue(), + if(!nodeService.exists(copyMapEntry.getValue())) { + continue; + } + String editorType = (String) nodeService.getProperty(copyMapEntry.getValue(), QName.createQName(CCConstants.CCM_PROP_EDITOR_TYPE)); - if ("tinymce".equals(editoryType)) { + if ("tinymce".equals(editorType)) { if (versionService.getVersionHistory(copyMapEntry.getValue()) == null) { Map transFormedProps = transformQNameKeyToString( nodeService.getProperties(copyMapEntry.getValue())); diff --git a/Backend/services/core/src/main/java/org/edu_sharing/spring/security/saml2/SecurityConfigurationSaml.java b/Backend/services/core/src/main/java/org/edu_sharing/spring/security/saml2/SecurityConfigurationSaml.java index 17ca8d26f..e77be3292 100644 --- a/Backend/services/core/src/main/java/org/edu_sharing/spring/security/saml2/SecurityConfigurationSaml.java +++ b/Backend/services/core/src/main/java/org/edu_sharing/spring/security/saml2/SecurityConfigurationSaml.java @@ -8,7 +8,6 @@ import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; import org.edu_sharing.alfresco.lightbend.LightbendConfigLoader; -import org.edu_sharing.repository.server.SecurityHeadersFilter; import org.edu_sharing.repository.server.tools.ApplicationInfoList; import org.edu_sharing.service.config.ConfigServiceFactory; import org.edu_sharing.spring.security.basic.CSRFConfig; @@ -16,10 +15,10 @@ import org.edu_sharing.spring.security.basic.EduWebSecurityCustomizer; import org.edu_sharing.spring.security.basic.HeadersConfig; import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; -import org.springframework.core.annotation.Order; import org.springframework.core.convert.converter.Converter; import org.springframework.core.io.ClassPathResource; import org.springframework.security.config.annotation.ObjectPostProcessor; @@ -35,6 +34,7 @@ import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; +import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; @@ -69,6 +69,9 @@ public class SecurityConfigurationSaml { Logger logger = Logger.getLogger(SecurityConfigurationSaml.class); + @Autowired(required = false) + Saml2LogoutRequestResolver logoutRequestResolver; + @Bean public WebSecurityCustomizer webSecurityCustomizer() { return EduWebSecurityCustomizer.webSecurityCustomizer(); @@ -108,6 +111,10 @@ SecurityFilterChain app(HttpSecurity http) throws Exception { CSRFConfig.config(http); HeadersConfig.config(http); + if(logoutRequestResolver != null){ + http.saml2Logout(logout -> logout.logoutRequest(r -> r.logoutRequestResolver(logoutRequestResolver))); + } + return http.build(); } diff --git a/Backend/services/core/src/main/java/org/edu_sharing/spring/security/saml2/SecurityConfigurationSamlEncryption.java b/Backend/services/core/src/main/java/org/edu_sharing/spring/security/saml2/SecurityConfigurationSamlEncryption.java new file mode 100644 index 000000000..c9556c3d3 --- /dev/null +++ b/Backend/services/core/src/main/java/org/edu_sharing/spring/security/saml2/SecurityConfigurationSamlEncryption.java @@ -0,0 +1,108 @@ +package org.edu_sharing.spring.security.saml2; + +import org.apache.xml.security.encryption.XMLCipherParameters; +import org.edu_sharing.spring.conditions.ConditionalOnProperty; +import org.opensaml.saml.saml2.core.EncryptedID; +import org.opensaml.saml.saml2.core.LogoutRequest; +import org.opensaml.saml.saml2.core.NameID; +import org.opensaml.saml.saml2.encryption.Encrypter; +import org.opensaml.security.credential.BasicCredential; +import org.opensaml.security.x509.BasicX509Credential; +import org.opensaml.xmlsec.encryption.support.DataEncryptionParameters; +import org.opensaml.xmlsec.encryption.support.EncryptionException; +import org.opensaml.xmlsec.encryption.support.KeyEncryptionParameters; +import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver; +import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; + +@ConditionalOnProperty(name = "security.sso.saml.encryptLogoutRequest", havingValue = "true") +@Configuration +public class SecurityConfigurationSamlEncryption { + @Bean + Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationRepository registrations) { + OpenSaml4LogoutRequestResolver logoutRequestResolver = + new OpenSaml4LogoutRequestResolver(registrations); + logoutRequestResolver.setParametersConsumer((parameters) -> { + String name = ((Saml2AuthenticatedPrincipal) parameters.getAuthentication().getPrincipal()).getFirstAttribute("CustomAttribute"); + String format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"; + LogoutRequest logoutRequest = parameters.getLogoutRequest(); + NameID nameId = logoutRequest.getNameID(); + nameId.setValue(name); + nameId.setFormat(format); + + RelyingPartyRegistration.AssertingPartyDetails assertingPartyDetails = parameters.getRelyingPartyRegistration().getAssertingPartyDetails(); + + Saml2X509Credential credential = (assertingPartyDetails.getEncryptionX509Credentials().size() > 0 ) + //when a key descriptor is defined in idp metadata + ? assertingPartyDetails.getEncryptionX509Credentials().iterator().next() + //else fallback to default certificate + : assertingPartyDetails.getVerificationX509Credentials().iterator().next(); + + EncryptedID encrypted = encrypted(nameId, credential); + logoutRequest.setEncryptedID(encrypted); + logoutRequest.setNameID(null); + }); + return logoutRequestResolver; + } + + static EncryptedID encrypted(NameID nameId, Saml2X509Credential credential) { + + Encrypter encrypter = getEncrypter(credential); + try { + return encrypter.encrypt(nameId); + } + catch (EncryptionException ex) { + throw new Saml2Exception("Unable to encrypt nameID.", ex); + } + } + + private static Encrypter getEncrypter(Saml2X509Credential credential) { + String dataAlgorithm = XMLCipherParameters.AES_256; + BasicCredential dataCredential = new BasicCredential(generateSecretKey()); + DataEncryptionParameters dataEncryptionParameters = new DataEncryptionParameters(); + dataEncryptionParameters.setEncryptionCredential(dataCredential); + dataEncryptionParameters.setAlgorithm(dataAlgorithm); + + String keyAlgorithm = XMLCipherParameters.RSA_OAEP; + KeyEncryptionParameters keyEncryptionParameters = new KeyEncryptionParameters(); + keyEncryptionParameters.setEncryptionCredential( new BasicX509Credential(credential.getCertificate())); + keyEncryptionParameters.setAlgorithm(keyAlgorithm); + + X509KeyInfoGeneratorFactory factory = new X509KeyInfoGeneratorFactory(); + factory.setEmitEntityIDAsKeyName(false); + factory.setEmitX509SubjectName(true); + + keyEncryptionParameters.setKeyInfoGenerator(factory.newInstance()); + dataEncryptionParameters.setKeyInfoGenerator(factory.newInstance()); + + Encrypter encrypter = new Encrypter(dataEncryptionParameters,keyEncryptionParameters); + encrypter.setKeyPlacement(Encrypter.KeyPlacement.INLINE); + + return encrypter; + } + + private static SecretKey generateSecretKey() { + KeyGenerator keyGen = null; + try { + keyGen = KeyGenerator.getInstance("AES"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + keyGen.init(256); // for AES-256 + SecretKey secretKey = keyGen.generateKey(); + return secretKey; + } + +} diff --git a/config/defaults/src/main/resources/edu-sharing.reference.conf b/config/defaults/src/main/resources/edu-sharing.reference.conf index 8e0d9ac4d..9690bb81c 100644 --- a/config/defaults/src/main/resources/edu-sharing.reference.conf +++ b/config/defaults/src/main/resources/edu-sharing.reference.conf @@ -115,6 +115,7 @@ security { // filePath: /tmp/metadata.xml } } + encryptLogoutRequest: false } // keycloak config // 1. go to realm -> Clients -> Create Client -> Client Type OpenID Connect, set a Client ID