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:
@@ -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);
|
||||
}
|
||||
|
||||
/***
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user