Introduce pluggable tenant resolver (#2151)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-12-18 15:17:35 +02:00
committed by GitHub
parent 1c211c81c2
commit ed93d3fc7b
8 changed files with 96 additions and 44 deletions

View File

@@ -16,9 +16,10 @@ import java.util.stream.Collectors;
import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.im.authentication.SpRole; import org.eclipse.hawkbit.im.authentication.SpRole;
import org.eclipse.hawkbit.tenancy.TenantAware.DefaultTenantResolver;
import org.eclipse.hawkbit.tenancy.TenantAware.TenantResolver;
import org.eclipse.hawkbit.tenancy.TenantAwareUserProperties; import org.eclipse.hawkbit.tenancy.TenantAwareUserProperties;
import org.eclipse.hawkbit.tenancy.TenantAwareUserProperties.User; import org.eclipse.hawkbit.tenancy.TenantAwareUserProperties.User;
import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties; import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.InMemoryUserAuthoritiesResolver; import org.eclipse.hawkbit.security.InMemoryUserAuthoritiesResolver;
import org.eclipse.hawkbit.security.MdcHandler; import org.eclipse.hawkbit.security.MdcHandler;
@@ -56,6 +57,12 @@ import org.springframework.util.CollectionUtils;
@EnableConfigurationProperties({ SecurityProperties.class, HawkbitSecurityProperties.class, TenantAwareUserProperties.class }) @EnableConfigurationProperties({ SecurityProperties.class, HawkbitSecurityProperties.class, TenantAwareUserProperties.class })
public class SecurityAutoConfiguration { public class SecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TenantResolver tenantResolver() {
return new DefaultTenantResolver();
}
/** /**
* Creates a {@link ContextAware} (hence {@link TenantAware}) bean based on the given {@link UserAuthoritiesResolver} and * Creates a {@link ContextAware} (hence {@link TenantAware}) bean based on the given {@link UserAuthoritiesResolver} and
* {@link SecurityContextSerializer}. * {@link SecurityContextSerializer}.
@@ -68,8 +75,9 @@ public class SecurityAutoConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
public ContextAware contextAware( public ContextAware contextAware(
final UserAuthoritiesResolver authoritiesResolver, final UserAuthoritiesResolver authoritiesResolver,
@Autowired(required = false) final SecurityContextSerializer securityContextSerializer) { @Autowired(required = false) final SecurityContextSerializer securityContextSerializer,
return new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer); @Autowired(required = false) final TenantResolver tenantResolver) {
return new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer, tenantResolver);
} }
/** /**

View File

@@ -9,6 +9,9 @@
*/ */
package org.eclipse.hawkbit.tenancy; package org.eclipse.hawkbit.tenancy;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
/** /**
* Interface for components that are aware of the application's current tenant. * Interface for components that are aware of the application's current tenant.
*/ */
@@ -66,4 +69,29 @@ public interface TenantAware {
*/ */
T run(); T run();
} }
/**
* Resolves the tenant from the current context.
*/
interface TenantResolver {
String resolveTenant();
}
class DefaultTenantResolver implements TenantResolver {
@Override
public String resolveTenant() {
final SecurityContext context = SecurityContextHolder.getContext();
if (context.getAuthentication() != null) {
final Object principal = context.getAuthentication().getPrincipal();
if (context.getAuthentication().getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails) {
return tenantAwareAuthenticationDetails.getTenant();
} else if (principal instanceof TenantAwareUser tenantAwareUser) {
return tenantAwareUser.getTenant();
}
}
return null;
}
}
} }

View File

@@ -49,9 +49,9 @@ public class JpaConfiguration extends JpaBaseConfiguration {
protected JpaConfiguration( protected JpaConfiguration(
final DataSource dataSource, final JpaProperties properties, final DataSource dataSource, final JpaProperties properties,
final ObjectProvider<JtaTransactionManager> jtaTransactionManagerProvider, final ObjectProvider<JtaTransactionManager> jtaTransactionManagerProvider,
final TenantAware tenantAware) { final TenantAware.TenantResolver tenantResolver) {
super(dataSource, properties, jtaTransactionManagerProvider); super(dataSource, properties, jtaTransactionManagerProvider);
tenantIdentifier = new TenantIdentifier(tenantAware); tenantIdentifier = new TenantIdentifier(tenantResolver);
} }
@Bean @Bean

View File

@@ -21,16 +21,16 @@ import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomi
*/ */
class TenantIdentifier implements CurrentTenantIdentifierResolver<String> { class TenantIdentifier implements CurrentTenantIdentifierResolver<String> {
private final TenantAware tenantAware; private final TenantAware.TenantResolver tenantResolver;
TenantIdentifier(final TenantAware tenantAware) { TenantIdentifier(final TenantAware.TenantResolver tenantResolver) {
this.tenantAware = tenantAware; this.tenantResolver = tenantResolver;
} }
@Override @Override
public String resolveCurrentTenantIdentifier() { public String resolveCurrentTenantIdentifier() {
// on bootstrapping hibernate requests tenant and want to be non-null // on bootstrapping hibernate requests tenant and want to be non-null
return Optional.ofNullable(tenantAware.getCurrentTenant()).map(String::toUpperCase).orElse(""); return Optional.ofNullable(tenantResolver.resolveTenant()).map(String::toUpperCase).orElse("");
} }
@Override @Override

View File

@@ -31,8 +31,9 @@
<id>eclipselink</id> <id>eclipselink</id>
<activation> <activation>
<property> <property>
<!-- default, if not set - eclipse link --> <!-- default, if not set (or not hibernate) - eclipse link -->
<name>!jpa.vendor</name> <name>jpa.vendor</name>
<value>!hibernate</value>
</property> </property>
</activation> </activation>

View File

@@ -43,6 +43,8 @@ import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SpringSecurityAuditorAware; import org.eclipse.hawkbit.security.SpringSecurityAuditorAware;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.TenantAware.DefaultTenantResolver;
import org.eclipse.hawkbit.tenancy.TenantAware.TenantResolver;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver; import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties; import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
@@ -165,9 +167,16 @@ public class TestConfiguration implements AsyncConfigurer {
} }
@Bean @Bean
ContextAware contextAware(final UserAuthoritiesResolver authoritiesResolver, final SecurityContextSerializer securityContextSerializer) { TenantResolver tenantResolver() {
return new DefaultTenantResolver();
}
@Bean
ContextAware contextAware(
final UserAuthoritiesResolver authoritiesResolver, final SecurityContextSerializer securityContextSerializer,
final TenantResolver tenantResolver) {
// allow spying the security context // allow spying the security context
return org.mockito.Mockito.spy(new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer)); return org.mockito.Mockito.spy(new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer, tenantResolver));
} }
@Bean @Bean

View File

@@ -161,8 +161,8 @@ public class RestConfiguration {
logRequest(request, ex); logRequest(request, ex);
final ExceptionInfo response = createExceptionInfo(ex); final ExceptionInfo response = createExceptionInfo(ex);
final HttpStatus responseStatus; final HttpStatus responseStatus;
if (ex instanceof AbstractServerRtException) { if (ex instanceof AbstractServerRtException abstractServerRtException) {
responseStatus = getStatusOrDefault(((AbstractServerRtException) ex).getError()); responseStatus = getStatusOrDefault(abstractServerRtException.getError());
} else { } else {
responseStatus = DEFAULT_RESPONSE_STATUS; responseStatus = DEFAULT_RESPONSE_STATUS;
} }
@@ -278,16 +278,20 @@ public class RestConfiguration {
} }
private void logRequest(final HttpServletRequest request, final Exception ex) { private void logRequest(final HttpServletRequest request, final Exception ex) {
log.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL()); if (log.isTraceEnabled()) {
log.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL(), ex);
} else {
log.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL());
}
} }
private ExceptionInfo createExceptionInfo(final Exception ex) { private ExceptionInfo createExceptionInfo(final Exception ex) {
final ExceptionInfo response = new ExceptionInfo(); final ExceptionInfo response = new ExceptionInfo();
response.setMessage(ex.getMessage()); response.setMessage(ex.getMessage());
response.setExceptionClass(ex.getClass().getName()); response.setExceptionClass(ex.getClass().getName());
if (ex instanceof AbstractServerRtException) { if (ex instanceof AbstractServerRtException abstractServerRtException) {
response.setErrorCode(((AbstractServerRtException) ex).getError().getKey()); response.setErrorCode(abstractServerRtException.getError().getKey());
response.setInfo(((AbstractServerRtException) ex).getInfo()); response.setInfo(abstractServerRtException.getInfo());
} }
return response; return response;
} }
@@ -305,10 +309,7 @@ public class RestConfiguration {
private final String[] excludeAntPaths; private final String[] excludeAntPaths;
private final AntPathMatcher antMatcher = new AntPathMatcher(); private final AntPathMatcher antMatcher = new AntPathMatcher();
/** public ExcludePathAwareShallowETagFilter(final String... excludeAntPaths) {
* @param excludeAntPaths
*/
public ExcludePathAwareShallowETagFilter(final String... excludeAntPaths) {
this.excludeAntPaths = excludeAntPaths; this.excludeAntPaths = excludeAntPaths;
} }

View File

@@ -43,20 +43,19 @@ public class SecurityContextTenantAware implements ContextAware {
public static final String SYSTEM_USER = "system"; public static final String SYSTEM_USER = "system";
private static final Collection<? extends GrantedAuthority> SYSTEM_AUTHORITIES = private static final Collection<? extends GrantedAuthority> SYSTEM_AUTHORITIES =
Collections.singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.SYSTEM_ROLE)); List.of(new SimpleGrantedAuthority(SpringEvalExpressions.SYSTEM_ROLE));
private final UserAuthoritiesResolver authoritiesResolver; private final UserAuthoritiesResolver authoritiesResolver;
private final SecurityContextSerializer securityContextSerializer; private final SecurityContextSerializer securityContextSerializer;
private final TenantResolver tenantResolver;
/** /**
* Creates the {@link SecurityContextTenantAware} based on the given {@link UserAuthoritiesResolver}. * Creates the {@link SecurityContextTenantAware} based on the given {@link UserAuthoritiesResolver}.
* *
* @param authoritiesResolver Resolver to retrieve the authorities for a given user. Must * @param authoritiesResolver Resolver to retrieve the authorities for a given user. Must not be <code>null</code>..
* not be <code>null</code>..
*/ */
public SecurityContextTenantAware(final UserAuthoritiesResolver authoritiesResolver) { public SecurityContextTenantAware(final UserAuthoritiesResolver authoritiesResolver) {
this.authoritiesResolver = authoritiesResolver; this(authoritiesResolver, null, null);
this.securityContextSerializer = SecurityContextSerializer.NOP;
} }
/** /**
@@ -65,24 +64,30 @@ public class SecurityContextTenantAware implements ContextAware {
* @param authoritiesResolver Resolver to retrieve the authorities for a given user. Must not be <code>null</code>. * @param authoritiesResolver Resolver to retrieve the authorities for a given user. Must not be <code>null</code>.
* @param securityContextSerializer Serializer that is used to serialize / deserialize {@link SecurityContext}s. * @param securityContextSerializer Serializer that is used to serialize / deserialize {@link SecurityContext}s.
*/ */
public SecurityContextTenantAware(final UserAuthoritiesResolver authoritiesResolver, public SecurityContextTenantAware(
final UserAuthoritiesResolver authoritiesResolver,
@Nullable final SecurityContextSerializer securityContextSerializer) { @Nullable final SecurityContextSerializer securityContextSerializer) {
this(authoritiesResolver, securityContextSerializer, null);
}
/**
* Creates the {@link SecurityContextTenantAware} based on the given {@link UserAuthoritiesResolver}.
*
* @param authoritiesResolver Resolver to retrieve the authorities for a given user. Must not be <code>null</code>.
* @param securityContextSerializer Serializer that is used to serialize / deserialize {@link SecurityContext}s.
*/
public SecurityContextTenantAware(
final UserAuthoritiesResolver authoritiesResolver,
@Nullable final SecurityContextSerializer securityContextSerializer,
@Nullable final TenantResolver tenantResolver) {
this.authoritiesResolver = authoritiesResolver; this.authoritiesResolver = authoritiesResolver;
this.securityContextSerializer = securityContextSerializer == null ? SecurityContextSerializer.NOP : securityContextSerializer; this.securityContextSerializer = securityContextSerializer == null ? SecurityContextSerializer.NOP : securityContextSerializer;
this.tenantResolver = tenantResolver == null ? new DefaultTenantResolver() : tenantResolver;
} }
@Override @Override
public String getCurrentTenant() { public String getCurrentTenant() {
final SecurityContext context = SecurityContextHolder.getContext(); return tenantResolver.resolveTenant();
if (context.getAuthentication() != null) {
final Object principal = context.getAuthentication().getPrincipal();
if (context.getAuthentication().getDetails() instanceof TenantAwareAuthenticationDetails) {
return ((TenantAwareAuthenticationDetails) context.getAuthentication().getDetails()).getTenant();
} else if (principal instanceof TenantAwareUser) {
return ((TenantAwareUser) principal).getTenant();
}
}
return null;
} }
@Override @Override
@@ -90,11 +95,11 @@ public class SecurityContextTenantAware implements ContextAware {
final SecurityContext context = SecurityContextHolder.getContext(); final SecurityContext context = SecurityContextHolder.getContext();
if (context.getAuthentication() != null) { if (context.getAuthentication() != null) {
final Object principal = context.getAuthentication().getPrincipal(); final Object principal = context.getAuthentication().getPrincipal();
if (principal instanceof OidcUser) { if (principal instanceof OidcUser oidcUser) {
return ((OidcUser) principal).getPreferredUsername(); return oidcUser.getPreferredUsername();
} }
if (principal instanceof User) { if (principal instanceof User user) {
return ((User) principal).getUsername(); return user.getUsername();
} }
} }
return null; return null;
@@ -243,4 +248,4 @@ public class SecurityContextTenantAware implements ContextAware {
delegate.setAuthenticated(isAuthenticated); delegate.setAuthenticated(isAuthenticated);
} }
} }
} }