Improve hawkBit user management (#1666)

1. Definded with properties users (static) are configured using property map (no need of indexes)
2. AuthenticationProvider that authenticates them is always registered (if not needed - don't configure them)
3. UserDetailsService (in case of missing - won't be registered)
4. Spring security user (spring.security.username) will be registered together with other users (if any). If any - it will be system-wide, otherwise tenant-scoped.
5. UserPrincipal renamed to TenantAwareUser in order to match its purpose.
6. Some if its fields are removes as not needed - to be closer to spring security user
7. DefaultRolloutApprovalStrategy now use UserAuthoritiesResolver instead of UserDetailsService as the central point of truth

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-02-26 16:56:37 +02:00
committed by GitHub
parent 783a5be2dd
commit 24d70827b7
16 changed files with 266 additions and 327 deletions

View File

@@ -12,18 +12,17 @@ package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.UserPrincipal;
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.util.StringUtils;
/**
@@ -34,16 +33,17 @@ import org.springframework.util.StringUtils;
*/
public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy {
private final UserDetailsService userDetailsService;
private final UserAuthoritiesResolver userAuthoritiesResolver;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final SystemSecurityContext systemSecurityContext;
DefaultRolloutApprovalStrategy(final UserDetailsService userDetailsService,
DefaultRolloutApprovalStrategy(
final UserAuthoritiesResolver userAuthoritiesResolver,
final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) {
this.userDetailsService = userDetailsService;
this.userAuthoritiesResolver = userAuthoritiesResolver;
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.systemSecurityContext = systemSecurityContext;
}
@@ -54,7 +54,7 @@ public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy {
*/
@Override
public boolean isApprovalNeeded(final Rollout rollout) {
return isApprovalEnabled() && hasNoApproveRolloutPermission(getActor(rollout).getAuthorities());
return isApprovalEnabled() && hasNoApproveRolloutPermission(getActorAuthorities(rollout));
}
private boolean isApprovalEnabled() {
@@ -62,7 +62,7 @@ public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy {
.getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue());
}
private UserDetails getActor(final Rollout rollout) {
private Collection<String> getActorAuthorities(final Rollout rollout) {
// rollout state transition from CREATING to CREATED is managed by
// scheduler under SYSTEM user context, thus we get the
// user based on the properties of initially created rollout entity
@@ -70,20 +70,21 @@ public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy {
final String actor = rollout.getLastModifiedBy() != null ? rollout.getLastModifiedBy()
: rollout.getCreatedBy();
if (!StringUtils.isEmpty(actor)) {
return systemSecurityContext.runAsSystem(() -> userDetailsService.loadUserByUsername(actor));
return systemSecurityContext.runAsSystem(
() -> userAuthoritiesResolver.getUserAuthorities(rollout.getTenant(), actor));
}
}
return (UserPrincipal) getCurrentAuthentication().getPrincipal();
return ((User) getCurrentAuthentication().getPrincipal()).getAuthorities().stream()
.map(GrantedAuthority::getAuthority).toList();
}
private static Authentication getCurrentAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
private static boolean hasNoApproveRolloutPermission(final Collection<? extends GrantedAuthority> authorities) {
return authorities.stream()
.noneMatch(authority -> SpPermission.APPROVE_ROLLOUT.equals(authority.getAuthority()));
private static boolean hasNoApproveRolloutPermission(final Collection<String> authorities) {
return authorities.stream().noneMatch(SpPermission.APPROVE_ROLLOUT::equals);
}
/***

View File

@@ -163,6 +163,7 @@ import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.hibernate.validator.BaseHibernateValidatorConfiguration;
import org.springframework.beans.BeansException;
@@ -193,7 +194,6 @@ import org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;
@@ -778,10 +778,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
RolloutApprovalStrategy rolloutApprovalStrategy(final UserDetailsService userDetailsService,
RolloutApprovalStrategy rolloutApprovalStrategy(final UserAuthoritiesResolver userAuthoritiesResolver,
final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) {
return new DefaultRolloutApprovalStrategy(userDetailsService, tenantConfigurationManagement,
return new DefaultRolloutApprovalStrategy(userAuthoritiesResolver, tenantConfigurationManagement,
systemSecurityContext);
}

View File

@@ -18,7 +18,7 @@ import java.util.concurrent.Callable;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.im.authentication.UserPrincipal;
import org.eclipse.hawkbit.im.authentication.UserTenantAware;
import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
@@ -163,8 +163,7 @@ public class SecurityContextSwitch {
authorities = annotation.authorities();
}
final TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken(
new UserPrincipal(annotation.principal(), annotation.principal(), annotation.principal(),
annotation.principal(), null, annotation.tenantId()),
new UserTenantAware(annotation.principal(), annotation.tenantId()),
annotation.credentials(), authorities);
testingAuthenticationToken.setDetails(
new TenantAwareAuthenticationDetails(annotation.tenantId(), annotation.controller()));

View File

@@ -25,7 +25,7 @@ import java.lang.annotation.Target;
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
@WithSecurityContext(factory = WithUser.WithUserPrincipalSecurityContextFactory.class)
@WithSecurityContext(factory = WithUser.WithTenantAwareUserSecurityContextFactory.class)
@Inherited
public @interface WithUser {
@@ -80,10 +80,10 @@ public @interface WithUser {
boolean controller() default false;
class WithUserPrincipalSecurityContextFactory implements WithSecurityContextFactory<WithUser> {
class WithTenantAwareUserSecurityContextFactory implements WithSecurityContextFactory<WithUser> {
@Override
public SecurityContext createSecurityContext(final WithUser withUserPrincipal) {
return new SecurityContextSwitch.WithUserSecurityContext(withUserPrincipal);
public SecurityContext createSecurityContext(final WithUser withTenantAwareUser) {
return new SecurityContextSwitch.WithUserSecurityContext(withTenantAwareUser);
}
}
}