Add AccessContext.asTenant and use where possible (#2838)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-11-27 16:27:56 +02:00
committed by GitHub
parent f6f62db0ad
commit 42384b7e31
17 changed files with 184 additions and 97 deletions

View File

@@ -12,6 +12,7 @@ package org.eclipse.hawkbit.context;
import java.io.Serial;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -78,10 +79,7 @@ public class AccessContext {
return null;
}
// Sometimes 'system' need to override the auditor when do create/modify actions in context of a tenant and user.
// Though this could be made using runAsTenantAsUser sometimes (as in transaction) this override is needed
// after runAsTenantAsUser (because it seems that auditor is got in commit time).
// So this thread local variable provides option to override explicitly the auditor.
// Sometimes 'system' need to override the auditor when do create/modify actions in context of an actor
private static final ThreadLocal<String> ACTOR_OVERRIDE = new ThreadLocal<>();
// Return the current actor / auditor / principal name. It could be a user (person), technical user, device, etc.
@@ -143,6 +141,34 @@ public class AccessContext {
}
}
/**
* Runs a given {@link Runnable} within a current authorities with set tenant in the context.
*
* @param tenant the tenant to be set in context.
* @param runnable the runnable to call within the tenant context
*/
public static void asTenant(final String tenant, final Runnable runnable) {
asTenant(tenant, () -> {
runnable.run();
return null;
});
}
/**
* Runs a given {@link Supplier} within a current authorities with set tenant in the context.
*
* @param tenant the tenant to be set in context.
* @param supplier the supplier to call within the tenant context
* @return the return value of the {@link Supplier#get()} method.
*/
public static <T> T asTenant(final String tenant, final Supplier<T> supplier) {
final SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(new AuthenticationDelegate(
tenant, Optional.ofNullable(actor()).orElse(SYSTEM_ACTOR),
SecurityContextHolder.getContext().getAuthentication()));
return withSecurityContext(securityContext, supplier);
}
public static void asActor(final String actor, final Runnable runnable) {
asActor(actor, () -> {
runnable.run();
@@ -219,14 +245,12 @@ public class AccessContext {
* @return the return value of the {@link Supplier#get()} method.
*/
public static <T> T asSystemAsTenant(final String tenant, final Supplier<T> supplier) {
final SecurityContext currentContext = SecurityContextHolder.getContext();
log.debug("Entering system code execution");
try {
log.debug("Entering system code execution");
final SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(new SystemCodeAuthentication(tenant));
return withSecurityContext(securityContext, supplier);
} finally {
SecurityContextHolder.setContext(currentContext);
log.debug("Leaving system code execution");
}
}
@@ -446,4 +470,83 @@ public class AccessContext {
throw new UnsupportedOperationException();
}
}
/**
* An {@link Authentication} implementation to delegate to an existing {@link Authentication} object except setting the details
* specifically for a specific tenant and user.
*/
private static final class AuthenticationDelegate implements Authentication {
@Serial
private static final long serialVersionUID = 1L;
private final Authentication delegate;
private final TenantAwareUser principal;
private final TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails;
private AuthenticationDelegate(final String tenant, final String username, final Authentication delegate) {
this.delegate = delegate;
principal = new TenantAwareUser(username, username, delegate != null ? delegate.getAuthorities() : Collections.emptyList(), tenant);
tenantAwareAuthenticationDetails = new TenantAwareAuthenticationDetails(tenant, false);
}
@Override
public int hashCode() {
return delegate != null ? delegate.hashCode() : -1;
}
@Override
public boolean equals(final Object another) {
if (another instanceof Authentication anotherAuthentication) {
return Objects.equals(delegate, anotherAuthentication) &&
Objects.equals(principal, anotherAuthentication.getPrincipal()) &&
Objects.equals(tenantAwareAuthenticationDetails, anotherAuthentication.getDetails());
} else {
return false;
}
}
@Override
public String toString() {
return delegate != null ? delegate.toString() : null;
}
@Override
public String getName() {
return delegate != null ? delegate.getName() : null;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return principal.getAuthorities();
}
@Override
public Object getCredentials() {
return delegate != null ? delegate.getCredentials() : null;
}
@Override
public Object getDetails() {
return tenantAwareAuthenticationDetails;
}
@Override
public Object getPrincipal() {
return principal;
}
@Override
public boolean isAuthenticated() {
return delegate == null || delegate.isAuthenticated();
}
@Override
public void setAuthenticated(final boolean isAuthenticated) {
if (delegate == null) {
return;
}
delegate.setAuthenticated(isAuthenticated);
}
}
}

View File

@@ -9,11 +9,11 @@
*/
package org.eclipse.hawkbit.context;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant;
import java.util.List;
import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.auth.SpRole;
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
import org.junit.jupiter.api.Test;
@@ -23,32 +23,36 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
class SystemSecurityContextTest {
class AccessContextAsSystemTest {
@Test
void test() {
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("test", "pass", List.of(new SimpleGrantedAuthority("anonymous")));
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
"test", "pass", List.of(new SimpleGrantedAuthority("anonymous")));
auth.setDetails("string details");
test(auth);
}
@Test
void testWithNullPrincipal() {
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(null, "pass", List.of(new SimpleGrantedAuthority("anonymous")));
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
null, "pass", List.of(new SimpleGrantedAuthority("anonymous")));
auth.setDetails("string details");
test(auth);
}
@Test
void testWithNullCredentials() {
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("test", null, List.of(new SimpleGrantedAuthority("anonymous")));
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
"test", null, List.of(new SimpleGrantedAuthority("anonymous")));
auth.setDetails("string details");
test(auth);
}
@Test
void testWitAllNull() {
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(null, null, List.of(new SimpleGrantedAuthority("anonymous")));
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
null, null, List.of(new SimpleGrantedAuthority("anonymous")));
auth.setDetails(null);
test(auth);
}
@@ -59,10 +63,10 @@ class SystemSecurityContextTest {
SecurityContextHolder.setContext(sc);
asSystemAsTenant("tenant", () -> {
final Authentication currentAuth = SecurityContextHolder.getContext().getAuthentication();
Assertions.assertThat(currentAuth.getClass().getSimpleName()).isEqualTo("SystemCodeAuthentication");
Assertions.assertThat(currentAuth.getCredentials()).isNull();
Assertions.assertThat(currentAuth.getAuthorities()).isEqualTo(List.of(new SimpleGrantedAuthority(SpRole.SYSTEM_ROLE)));
Assertions.assertThat(currentAuth.getDetails()).isEqualTo(new TenantAwareAuthenticationDetails("tenant", false));
assertThat(currentAuth.getClass().getSimpleName()).isEqualTo("SystemCodeAuthentication");
assertThat(currentAuth.getCredentials()).isNull();
assertThat(currentAuth.getAuthorities()).isEqualTo(List.of(new SimpleGrantedAuthority(SpRole.SYSTEM_ROLE)));
assertThat(currentAuth.getDetails()).isEqualTo(new TenantAwareAuthenticationDetails("tenant", false));
});
SecurityContextHolder.clearContext();
}