From 311922c4aadae5a5fa84fc3fc3a70475ec714d52 Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Thu, 29 Feb 2024 15:18:44 +0200 Subject: [PATCH] Move static config based auth provider in security-core (#1671) Signed-off-by: Marinov Avgustin --- ...MemoryUserManagementAutoConfiguration.java | 153 ++---------------- .../security/SecurityAutoConfiguration.java | 3 +- .../StaticAuthenticationProvider.java | 146 +++++++++++++++++ .../TenantAwareUserProperties.java | 2 +- 4 files changed, 158 insertions(+), 146 deletions(-) create mode 100644 hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/StaticAuthenticationProvider.java rename {hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security => hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication}/TenantAwareUserProperties.java (94%) diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/InMemoryUserManagementAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/InMemoryUserManagementAutoConfiguration.java index a5d2f4bd0..a024a3246 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/InMemoryUserManagementAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/InMemoryUserManagementAutoConfiguration.java @@ -9,19 +9,9 @@ */ package org.eclipse.hawkbit.autoconfigure.security; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Optional; -import java.util.function.Supplier; -import java.util.regex.Pattern; - import org.eclipse.hawkbit.im.authentication.MultitenancyIndicator; -import org.eclipse.hawkbit.im.authentication.PermissionUtils; -import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; -import org.eclipse.hawkbit.im.authentication.TenantAwareUser; +import org.eclipse.hawkbit.im.authentication.StaticAuthenticationProvider; +import org.eclipse.hawkbit.im.authentication.TenantAwareUserProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -29,19 +19,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.util.ObjectUtils; + +import java.util.Optional; /** * Autoconfiguration for the in-memory-user-management. @@ -51,123 +33,18 @@ import org.springframework.util.ObjectUtils; @EnableConfigurationProperties({ TenantAwareUserProperties.class }) public class InMemoryUserManagementAutoConfiguration extends GlobalAuthenticationConfigurerAdapter { - private static final String DEFAULT_TENANT = "DEFAULT"; + private final StaticAuthenticationProvider authenticationProvider; - private final UserDetailsService userDetailsService; - - InMemoryUserManagementAutoConfiguration( - final SecurityProperties securityProperties, + InMemoryUserManagementAutoConfiguration(final SecurityProperties securityProperties, final TenantAwareUserProperties tenantAwareUserProperties, final Optional passwordEncoder) { - userDetailsService = userDetailsService( - securityProperties, tenantAwareUserProperties, passwordEncoder.orElse(null)); + authenticationProvider = new StaticAuthenticationProvider(tenantAwareUserProperties, securityProperties, + passwordEncoder.orElse(null)); } @Override public void configure(final AuthenticationManagerBuilder auth) { - final DaoAuthenticationProvider userDaoAuthenticationProvider = new TenantDaoAuthenticationProvider(); - userDaoAuthenticationProvider.setUserDetailsService(userDetailsService); - auth.authenticationProvider(userDaoAuthenticationProvider); - } - - private static UserDetailsService userDetailsService( - final SecurityProperties securityProperties, - final TenantAwareUserProperties tenantAwareUserProperties, - final PasswordEncoder passwordEncoder) { - final List userPrincipals = new ArrayList<>(); - tenantAwareUserProperties.getUsers().forEach((username, user) -> { - final TenantAwareUser userPrincipal = new TenantAwareUser( - username, password(user.getPassword(), passwordEncoder), - createAuthorities(user.getRoles(), user.getPermissions(), Collections::emptyList), - ObjectUtils.isEmpty(user.getTenant()) ? DEFAULT_TENANT : user.getTenant()); - userPrincipals.add(userPrincipal); - }); - - // If no tenant users are configured through the tenant user properties, set up - // the default user from spring security properties as super DEFAULT tenant user - if (userPrincipals.isEmpty()) { - userPrincipals - .add(new TenantAwareUser( - securityProperties.getUser().getName(), - password(securityProperties.getUser().getPassword(), passwordEncoder), - createAuthorities( - securityProperties.getUser().getRoles(), Collections.emptyList(), - PermissionUtils::createAllAuthorityList), - DEFAULT_TENANT)); - } else if (securityProperties != null && securityProperties.getUser() != null && - !securityProperties.getUser().isPasswordGenerated()) { - // otherwise if the security user is explicitly setup (no autogenerated password) - // set it up as generic non tenant user - userPrincipals - .add(new User( - securityProperties.getUser().getName(), - password(securityProperties.getUser().getPassword(), passwordEncoder), - createAuthorities( - securityProperties.getUser().getRoles(), Collections.emptyList(), - PermissionUtils::createAllAuthorityList))); - } - - return new FixedInMemoryTenantAwareUserDetailsService(userPrincipals); - } - - private static String password(final String password, final PasswordEncoder passwordEncoder) { - return passwordEncoder == null && !Pattern.compile("^\\{.+}.*$").matcher(password).matches() ? - "{noop}" + password : password; - } - - private static List createAuthorities( - final List userRoles, final List userPermissions, - final Supplier> defaultRolesSupplier) { - if (ObjectUtils.isEmpty(userRoles) && ObjectUtils.isEmpty(userPermissions)) { - return defaultRolesSupplier.get(); - } - - final List grantedAuthorityList = new ArrayList<>(); - if (userRoles != null) { - for (final String role : userRoles) { - grantedAuthorityList.add(new SimpleGrantedAuthority("ROLE_" + role)); - } - } - // Allows ALL as a shorthand for all permissions - if (userPermissions.size() == 1 && "ALL".equals(userPermissions.get(0))) { - grantedAuthorityList.addAll(PermissionUtils.createAllAuthorityList()); - } else { - for (final String permission : userPermissions) { - grantedAuthorityList.add(new SimpleGrantedAuthority(permission)); - } - } - - return grantedAuthorityList; - } - - private static class FixedInMemoryTenantAwareUserDetailsService implements UserDetailsService { - - private final HashMap userMap = new HashMap<>(); - - private FixedInMemoryTenantAwareUserDetailsService(final Collection userPrincipals) { - for (final User user : userPrincipals) { - userMap.put(user.getUsername(), user); - } - } - - @Override - public UserDetails loadUserByUsername(final String username) { - final User user = userMap.get(username); - if (user == null) { - throw new UsernameNotFoundException("No such user"); - } - // Spring mutates the data, so we must return a copy here - return clone(user); - } - - private static User clone(final User user) { - if (user instanceof TenantAwareUser) { - return new TenantAwareUser(user.getUsername(), user.getPassword(), user.getAuthorities(), - ((TenantAwareUser)user).getTenant()); - } else { - return new User(user.getUsername(), user.getPassword(), user.getAuthorities()); - } - } + auth.authenticationProvider(authenticationProvider); } /** @@ -178,16 +55,4 @@ public class InMemoryUserManagementAutoConfiguration extends GlobalAuthenticatio MultitenancyIndicator multiTenancyIndicator() { return () -> false; } - - private static class TenantDaoAuthenticationProvider extends DaoAuthenticationProvider { - - @Override - protected Authentication createSuccessAuthentication(final Object principal, - final Authentication authentication, final UserDetails user) { - final UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, - authentication.getCredentials(), user.getAuthorities()); - result.setDetails(new TenantAwareAuthenticationDetails(DEFAULT_TENANT, false)); - return result; - } - } } \ No newline at end of file diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java index 3b5288651..87677e76d 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java @@ -15,7 +15,8 @@ import java.util.Map; import java.util.stream.Collectors; import org.eclipse.hawkbit.ContextAware; -import org.eclipse.hawkbit.autoconfigure.security.TenantAwareUserProperties.User; +import org.eclipse.hawkbit.im.authentication.TenantAwareUserProperties; +import org.eclipse.hawkbit.im.authentication.TenantAwareUserProperties.User; import org.eclipse.hawkbit.im.authentication.PermissionService; import org.eclipse.hawkbit.security.DdiSecurityProperties; import org.eclipse.hawkbit.security.InMemoryUserAuthoritiesResolver; diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/StaticAuthenticationProvider.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/StaticAuthenticationProvider.java new file mode 100644 index 000000000..a2d19dcae --- /dev/null +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/StaticAuthenticationProvider.java @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.im.authentication; + +import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.function.Supplier; +import java.util.regex.Pattern; + +/** + * Authentication provider for configured via spring application properties users. + * The users could be tenant scoped or global. + */ +public class StaticAuthenticationProvider extends DaoAuthenticationProvider { + + public StaticAuthenticationProvider( + final TenantAwareUserProperties tenantAwareUserProperties, final SecurityProperties securityProperties, + final PasswordEncoder passwordEncoder) { + setUserDetailsService(userDetailsService(securityProperties, tenantAwareUserProperties, passwordEncoder)); + } + + @Override + protected Authentication createSuccessAuthentication(final Object principal, + final Authentication authentication, final UserDetails user) { + final UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken( + principal, authentication.getCredentials(), user.getAuthorities()); + result.setDetails( + user instanceof TenantAwareUser tenantAwareUser ? + new TenantAwareAuthenticationDetails(tenantAwareUser.getTenant(), false) : + user); + return result; + } + + private static UserDetailsService userDetailsService( + final SecurityProperties securityProperties, + final TenantAwareUserProperties tenantAwareUserProperties, + final PasswordEncoder passwordEncoder) { + final List userPrincipals = new ArrayList<>(); + tenantAwareUserProperties.getUsers().forEach((username, user) -> { + final String password = password(user.getPassword(), passwordEncoder); + final List credentials = + createAuthorities(user.getRoles(), user.getPermissions(), Collections::emptyList); + if (ObjectUtils.isEmpty(user.getTenant())) { + userPrincipals.add(new User(username, password, credentials)); + } else { + userPrincipals.add(new TenantAwareUser(username, password, credentials, user.getTenant())); + } + }); + + if (securityProperties != null && securityProperties.getUser() != null && + !securityProperties.getUser().isPasswordGenerated()) { + // explicitly setup system user - add is as a regular (non-tenant scoped) user + userPrincipals.add(new User( + securityProperties.getUser().getName(), + password(securityProperties.getUser().getPassword(), passwordEncoder), + createAuthorities( + securityProperties.getUser().getRoles(), Collections.emptyList(), + PermissionUtils::createAllAuthorityList))); + } + + return new FixedInMemoryTenantAwareUserDetailsService(userPrincipals); + } + + private static String password(final String password, final PasswordEncoder passwordEncoder) { + return passwordEncoder == null && !Pattern.compile("^\\{.+}.*$").matcher(password).matches() ? + "{noop}" + password : password; + } + + private static List createAuthorities( + final List userRoles, final List userPermissions, + final Supplier> defaultRolesSupplier) { + if (ObjectUtils.isEmpty(userRoles) && ObjectUtils.isEmpty(userPermissions)) { + return defaultRolesSupplier.get(); + } + + final List grantedAuthorityList = new ArrayList<>(); + if (userRoles != null) { + for (final String role : userRoles) { + grantedAuthorityList.add(new SimpleGrantedAuthority("ROLE_" + role)); + } + } + // Allows ALL as a shorthand for all permissions + if (userPermissions.size() == 1 && "ALL".equals(userPermissions.get(0))) { + grantedAuthorityList.addAll(PermissionUtils.createAllAuthorityList()); + } else { + for (final String permission : userPermissions) { + grantedAuthorityList.add(new SimpleGrantedAuthority(permission)); + } + } + + return grantedAuthorityList; + } + + private static class FixedInMemoryTenantAwareUserDetailsService implements UserDetailsService { + + private final HashMap userMap = new HashMap<>(); + + private FixedInMemoryTenantAwareUserDetailsService(final Collection userPrincipals) { + for (final User user : userPrincipals) { + userMap.put(user.getUsername(), user); + } + } + + @Override + public UserDetails loadUserByUsername(final String username) { + final User user = userMap.get(username); + if (user == null) { + throw new UsernameNotFoundException("No such user"); + } + // Spring mutates the data, so we must return a copy here + return clone(user); + } + + private static User clone(final User user) { + if (user instanceof TenantAwareUser) { + return new TenantAwareUser(user.getUsername(), user.getPassword(), user.getAuthorities(), + ((TenantAwareUser)user).getTenant()); + } else { + return new User(user.getUsername(), user.getPassword(), user.getAuthorities()); + } + } + } +} \ No newline at end of file diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/TenantAwareUserProperties.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/TenantAwareUserProperties.java similarity index 94% rename from hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/TenantAwareUserProperties.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/TenantAwareUserProperties.java index eb5cac4f9..ec03946ae 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/TenantAwareUserProperties.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/TenantAwareUserProperties.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.autoconfigure.security; +package org.eclipse.hawkbit.im.authentication; import java.util.ArrayList; import java.util.HashMap;