diff --git a/build.yaml b/build.yaml index fd354a08..0a7a9d1f 100644 --- a/build.yaml +++ b/build.yaml @@ -5,4 +5,4 @@ additionalTestConfigs: mavenAdditionalArgs: -Dtest=none -DfailIfNoTests=false -DminVersion=4.3.0 jdk17: testJdkTool: OPEN-JDK17 - mavenAdditionalArgs: -Dtest=none -DfailIfNoTests=false -DminVersion=4.6.0 -DruntimeVersion=4.6.0-rc2 -DruntimeProduct=MULE_EE -Dmule.jvm.version.extension.enforcement=LOOSE + mavenAdditionalArgs: -Dtest=none -DfailIfNoTests=false -DminVersion=4.6.0 -Dmule.jvm.version.extension.enforcement=LOOSE diff --git a/module/pom.xml b/module/pom.xml index 0c14fc63..05445b54 100644 --- a/module/pom.xml +++ b/module/pom.xml @@ -16,17 +16,17 @@ src/test/munit ${basedir}/target/test-mule/munit - 1.2.0-rc2 - 3.1.0-rc3 - 1.2.0-rc1 + 1.4.0 + 3.4.0 + 1.2.0 3.0.2 - 0.8.10 + 0.8.12 0.7.0 - 1.8.1 - 1.2.12 - 1.2.3 + 1.9.3 + 1.2.13 + 1.2.5 diff --git a/module/src/main/java/org/mule/extension/spring/api/SpringConfig.java b/module/src/main/java/org/mule/extension/spring/api/SpringConfig.java index e52c552b..821e79eb 100644 --- a/module/src/main/java/org/mule/extension/spring/api/SpringConfig.java +++ b/module/src/main/java/org/mule/extension/spring/api/SpringConfig.java @@ -163,7 +163,7 @@ public Map getObjectsByType(Class type) { @Override public void dispose() { if (applicationContext != null) { - applicationContext.destroy(); + applicationContext.close(); applicationContext = null; } } diff --git a/module/src/main/java/org/mule/extension/spring/internal/beanfactory/ArtifactObjectsAwareBeanFactory.java b/module/src/main/java/org/mule/extension/spring/internal/beanfactory/ArtifactObjectsAwareBeanFactory.java index 4f6accb7..810ed5e7 100644 --- a/module/src/main/java/org/mule/extension/spring/internal/beanfactory/ArtifactObjectsAwareBeanFactory.java +++ b/module/src/main/java/org/mule/extension/spring/internal/beanfactory/ArtifactObjectsAwareBeanFactory.java @@ -6,6 +6,16 @@ */ package org.mule.extension.spring.internal.beanfactory; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; + +import org.apache.commons.logging.LogFactory; +import org.mule.extension.spring.internal.util.CustomPostAuthenticationChecks; +import org.mule.extension.spring.internal.util.CustomPreAuthenticationChecks; +import org.mule.runtime.api.exception.MuleRuntimeException; import org.mule.runtime.api.ioc.ObjectProvider; import org.mule.runtime.api.lifecycle.Disposable; import org.mule.runtime.api.lifecycle.Initialisable; @@ -19,13 +29,24 @@ import java.util.Set; import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValue; import org.springframework.beans.TypeConverter; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.cache.NullUserCache; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.DelegatingPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; /** * {@link DefaultListableBeanFactory} implementation that takes into account the objects provided by the mule artifact for @@ -84,6 +105,11 @@ public Object doResolveDependency(DependencyDescriptor descriptor, String beanNa */ @Override protected T doGetBean(String name, Class requiredType, Object[] args, boolean typeCheckOnly) throws BeansException { + if ("fips140-2".equals(System.getProperty("mule.security.model"))) { + if (name.contains(DaoAuthenticationProvider.class.getName()) || DaoAuthenticationProvider.class.equals(requiredType)) { + return (T) authenticationProvider(getUserDetailService(name)); + } + } if (containsBean(name) || !artifactObjectProvider.containsObject(name) || destroying) { return super.doGetBean(name, requiredType, args, typeCheckOnly); } else { @@ -134,4 +160,59 @@ private boolean implementsLifecycle(BeanDefinition beanDefinition) { public void markForDestroy() { this.destroying = true; } + + /** + * TODO improve this code error check, ugly hack to bypass non FIPS compliant security algorithms + * + * The use of sun.misc.Unsafe API is discouraged. For the time being, using sun.misc.Unsafe to instantiate + * DaoAuthenticationProvider is a necessary workaround to avoid FIPS compliance issues caused by MD5 and SHA1. + * However, once we upgrade to Spring 6.x, we can switch to using the new constructor, passing a compliant PasswordEncoder. + * This will allow us to address the compliance issue more cleanly and avoid reliance on unsafe practices. + */ + static DaoAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService) { + try { + Class c = Class.forName("sun.misc.Unsafe"); + Field field = Arrays.stream(c.getDeclaredFields()).filter(f -> f.getName().equals("theUnsafe")) + .findFirst().orElseThrow(() -> new RuntimeException("Field not found")); + field.setAccessible(true); + Method allocateInstance = c.getDeclaredMethod("allocateInstance", Class.class); + DaoAuthenticationProvider authProvider = + (DaoAuthenticationProvider) allocateInstance.invoke(field.get(null), DaoAuthenticationProvider.class); + authProvider.setPasswordEncoder(createDelegatingPasswordEncoder()); + authProvider.setUserDetailsService(userDetailsService); + authProvider.setUserCache(new NullUserCache()); + authProvider.setAuthoritiesMapper(new NullAuthoritiesMapper()); + + // Setting custom pre and post authentication checks + authProvider.setPreAuthenticationChecks(new CustomPreAuthenticationChecks()); + authProvider.setPostAuthenticationChecks(new CustomPostAuthenticationChecks()); + + Field loggerField = AbstractUserDetailsAuthenticationProvider.class.getDeclaredField("logger"); + loggerField.setAccessible(true); + loggerField.set(authProvider, LogFactory.getLog(AbstractUserDetailsAuthenticationProvider.class)); + + return authProvider; + } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException | InvocationTargetException + | NoSuchFieldException e) { + throw new MuleRuntimeException(e); + } + } + + static PasswordEncoder createDelegatingPasswordEncoder() { + String encodingId = "bcrypt"; + Map encoders = new HashMap<>(); + encoders.put(encodingId, new BCryptPasswordEncoder()); + encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()); + return new DelegatingPasswordEncoder(encodingId, encoders); + } + + private UserDetailsService getUserDetailService(String name) { + List propertyValues = super.getBeanDefinition(name).getPropertyValues().getPropertyValueList(); + String userServiceName = propertyValues.stream() + .filter(propertyValue -> "userDetailsService".equals(propertyValue.getName())) + .map(propertyValue -> ((RuntimeBeanReference) propertyValue.getValue()).getBeanName()) + .findFirst() + .orElse(null); + return (UserDetailsService) super.getBean(userServiceName != null ? userServiceName : "userService"); + } } diff --git a/module/src/main/java/org/mule/extension/spring/internal/util/CustomPostAuthenticationChecks.java b/module/src/main/java/org/mule/extension/spring/internal/util/CustomPostAuthenticationChecks.java new file mode 100644 index 00000000..a6810d22 --- /dev/null +++ b/module/src/main/java/org/mule/extension/spring/internal/util/CustomPostAuthenticationChecks.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.extension.spring.internal.util; + +import org.springframework.security.authentication.CredentialsExpiredException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsChecker; + +public class CustomPostAuthenticationChecks implements UserDetailsChecker { + + @Override + public void check(UserDetails user) { + if (!user.isCredentialsNonExpired()) { + throw new CredentialsExpiredException("User credentials have expired"); + } + } +} diff --git a/module/src/main/java/org/mule/extension/spring/internal/util/CustomPreAuthenticationChecks.java b/module/src/main/java/org/mule/extension/spring/internal/util/CustomPreAuthenticationChecks.java new file mode 100644 index 00000000..c65e8575 --- /dev/null +++ b/module/src/main/java/org/mule/extension/spring/internal/util/CustomPreAuthenticationChecks.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.extension.spring.internal.util; + +import org.springframework.security.authentication.AccountExpiredException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.LockedException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsChecker; + +public class CustomPreAuthenticationChecks implements UserDetailsChecker { + + @Override + public void check(UserDetails user) { + if (!user.isAccountNonLocked()) { + throw new LockedException("User account is locked"); + } + if (!user.isEnabled()) { + throw new DisabledException("User is disabled"); + } + if (!user.isAccountNonExpired()) { + throw new AccountExpiredException("User account has expired"); + } + } +} diff --git a/module/src/test/resources/beans.xml b/module/src/test/resources/beans.xml index 915702af..57448628 100644 --- a/module/src/test/resources/beans.xml +++ b/module/src/test/resources/beans.xml @@ -14,8 +14,8 @@ - - + + diff --git a/src/test/munit/.gitkeep b/src/test/munit/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/.gitkeep b/src/test/resources/.gitkeep new file mode 100644 index 00000000..e69de29b