Introduce pluggable tenant resolver (#2151)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user