Improved AccessContext (#3029)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -12,6 +12,7 @@ package org.eclipse.hawkbit.autoconfigure.security;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.audit.AuditLoggingAspect;
|
import org.eclipse.hawkbit.audit.AuditLoggingAspect;
|
||||||
|
import org.eclipse.hawkbit.auth.SpRole;
|
||||||
import org.eclipse.hawkbit.context.AccessContext;
|
import org.eclipse.hawkbit.context.AccessContext;
|
||||||
import org.eclipse.hawkbit.repository.RepositoryConfiguration;
|
import org.eclipse.hawkbit.repository.RepositoryConfiguration;
|
||||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||||
@@ -47,7 +48,7 @@ public class SecurityAutoConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public AuditorAware<String> auditorAware() {
|
public AuditorAware<String> auditorAware() {
|
||||||
return () -> Optional.ofNullable(AccessContext.actor());
|
return () -> Optional.ofNullable(SpRole.isController() ? "CONTROLLER_PLUG_AND_PLAY" : AccessContext.actor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -9,9 +9,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.auth;
|
package org.eclipse.hawkbit.auth;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Software provisioning roles that implies set of permissions and reflects high-level roles.
|
* Software provisioning roles that implies set of permissions and reflects high-level roles.
|
||||||
@@ -29,6 +36,7 @@ public final class SpRole {
|
|||||||
public static final String SYSTEM_ROLE = "ROLE_SYSTEM_CODE";
|
public static final String SYSTEM_ROLE = "ROLE_SYSTEM_CODE";
|
||||||
/** The role which contains in the spring security context in case a controller is authenticated */
|
/** The role which contains in the spring security context in case a controller is authenticated */
|
||||||
public static final String CONTROLLER_ROLE = "ROLE_CONTROLLER";
|
public static final String CONTROLLER_ROLE = "ROLE_CONTROLLER";
|
||||||
|
public static final Collection<GrantedAuthority> CONTROLLER_AUTHORITIES = List.of(new SimpleGrantedAuthority(CONTROLLER_ROLE));
|
||||||
|
|
||||||
private static final String IMPLIES = " > ";
|
private static final String IMPLIES = " > ";
|
||||||
private static final String LINE_BREAK = "\n";
|
private static final String LINE_BREAK = "\n";
|
||||||
@@ -85,4 +93,16 @@ public final class SpRole {
|
|||||||
TENANT_ADMIN_HIERARCHY +
|
TENANT_ADMIN_HIERARCHY +
|
||||||
SYSTEM_ROLE_HIERARCHY;
|
SYSTEM_ROLE_HIERARCHY;
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
|
public static boolean isController() {
|
||||||
|
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
if (authentication == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||||||
|
if (authorities == CONTROLLER_AUTHORITIES) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return authorities.size() == 1 && CONTROLLER_ROLE.equals(authorities.iterator().next().getAuthority());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -17,8 +17,8 @@ import java.util.List;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
import lombok.Data;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareUser;
|
import org.eclipse.hawkbit.context.Principal;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareUserProperties;
|
import org.eclipse.hawkbit.tenancy.TenantAwareUserProperties;
|
||||||
import org.jspecify.annotations.NonNull;
|
import org.jspecify.annotations.NonNull;
|
||||||
import org.springframework.boot.security.autoconfigure.SecurityProperties;
|
import org.springframework.boot.security.autoconfigure.SecurityProperties;
|
||||||
@@ -47,12 +47,11 @@ public class StaticAuthenticationProvider extends DaoAuthenticationProvider {
|
|||||||
@Override
|
@Override
|
||||||
protected @NonNull Authentication createSuccessAuthentication(
|
protected @NonNull Authentication createSuccessAuthentication(
|
||||||
@NonNull final Object principal, final Authentication authentication, final UserDetails user) {
|
@NonNull final Object principal, final Authentication authentication, final UserDetails user) {
|
||||||
final UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
|
return new UsernamePasswordAuthenticationToken(
|
||||||
principal, authentication.getCredentials(), user.getAuthorities());
|
user instanceof TenantAwareUser tenantAwareUser
|
||||||
result.setDetails(user instanceof TenantAwareUser tenantAwareUser
|
? new Principal(tenantAwareUser.getTenant(), tenantAwareUser.getUsername())
|
||||||
? new TenantAwareAuthenticationDetails(tenantAwareUser.getTenant(), false)
|
: principal,
|
||||||
: user);
|
authentication.getCredentials(), user.getAuthorities());
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UserDetailsService userDetailsService(
|
private static UserDetailsService userDetailsService(
|
||||||
@@ -61,9 +60,7 @@ public class StaticAuthenticationProvider extends DaoAuthenticationProvider {
|
|||||||
tenantAwareUserProperties.getUser().forEach((username, user) -> {
|
tenantAwareUserProperties.getUser().forEach((username, user) -> {
|
||||||
final String password = password(user.getPassword());
|
final String password = password(user.getPassword());
|
||||||
final List<GrantedAuthority> credentials = createAuthorities(user.getRoles(), user.getPermissions(), Collections::emptyList);
|
final List<GrantedAuthority> credentials = createAuthorities(user.getRoles(), user.getPermissions(), Collections::emptyList);
|
||||||
userPrincipals.add(ObjectUtils.isEmpty(user.getTenant())
|
userPrincipals.add(new TenantAwareUser(username, password, credentials, user.getTenant()));
|
||||||
? new User(username, password, credentials)
|
|
||||||
: new TenantAwareUser(username, password, credentials, user.getTenant()));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (securityProperties != null && !securityProperties.getUser().isPasswordGenerated()) {
|
if (securityProperties != null && !securityProperties.getUser().isPasswordGenerated()) {
|
||||||
@@ -136,4 +133,20 @@ public class StaticAuthenticationProvider extends DaoAuthenticationProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class TenantAwareUser extends User {
|
||||||
|
|
||||||
|
private final String tenant;
|
||||||
|
|
||||||
|
private TenantAwareUser(
|
||||||
|
final String username, final String password, final Collection<? extends GrantedAuthority> authorities, final String tenant) {
|
||||||
|
super(username, password, authorities);
|
||||||
|
this.tenant = tenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTenant() {
|
||||||
|
return tenant;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,6 @@ package org.eclipse.hawkbit.context;
|
|||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -25,8 +24,7 @@ import lombok.Data;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.auth.SpRole;
|
import org.eclipse.hawkbit.auth.SpRole;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
import org.jspecify.annotations.NonNull;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareUser;
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
@@ -57,7 +55,7 @@ public class AccessContext {
|
|||||||
* @return could be empty if there is nothing to serialize or context aware is not supported.
|
* @return could be empty if there is nothing to serialize or context aware is not supported.
|
||||||
*/
|
*/
|
||||||
public static Optional<String> securityContext() {
|
public static Optional<String> securityContext() {
|
||||||
return Optional.ofNullable(SecurityContextHolder.getContext()).map(AccessContext::serialize);
|
return Optional.of(SecurityContextHolder.getContext()).map(AccessContext::serialize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,9 +67,7 @@ public class AccessContext {
|
|||||||
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
if (authentication != null) {
|
if (authentication != null) {
|
||||||
final Object principal = authentication.getPrincipal();
|
final Object principal = authentication.getPrincipal();
|
||||||
if (authentication.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails) {
|
if (principal instanceof Principal tenantAwareUser) {
|
||||||
return tenantAwareAuthenticationDetails.tenant();
|
|
||||||
} else if (principal instanceof TenantAwareUser tenantAwareUser) {
|
|
||||||
return tenantAwareUser.getTenant();
|
return tenantAwareUser.getTenant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,12 +266,9 @@ public class AccessContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String resolve(final Authentication authentication) {
|
private static String resolve(final Authentication authentication) {
|
||||||
if (authentication.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareDetails && tenantAwareDetails.controller()) {
|
|
||||||
return "CONTROLLER_PLUG_AND_PLAY";
|
|
||||||
}
|
|
||||||
final Object principal = authentication.getPrincipal();
|
final Object principal = authentication.getPrincipal();
|
||||||
if (principal instanceof ActorAware actorAware) {
|
if (principal instanceof Principal hawkbitPrincipal) {
|
||||||
return actorAware.getActor();
|
return hawkbitPrincipal.getActor();
|
||||||
}
|
}
|
||||||
if (principal instanceof UserDetails userDetails) {
|
if (principal instanceof UserDetails userDetails) {
|
||||||
return userDetails.getUsername();
|
return userDetails.getUsername();
|
||||||
@@ -316,11 +309,6 @@ public class AccessContext {
|
|||||||
return authentication == null || !authentication.isAuthenticated() || authentication.getPrincipal() == null;
|
return authentication == null || !authentication.isAuthenticated() || authentication.getPrincipal() == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ActorAware {
|
|
||||||
|
|
||||||
String getActor();
|
|
||||||
}
|
|
||||||
|
|
||||||
// simplified info for the security context keeping just the basic info needed for background execution of
|
// simplified info for the security context keeping just the basic info needed for background execution of
|
||||||
// controller authentication is not supported - always is false
|
// controller authentication is not supported - always is false
|
||||||
// only authenticated user is supported
|
// only authenticated user is supported
|
||||||
@@ -343,16 +331,12 @@ public class AccessContext {
|
|||||||
if (!authentication.isAuthenticated()) {
|
if (!authentication.isAuthenticated()) {
|
||||||
throw new IllegalStateException("Only authenticated context could be serialized");
|
throw new IllegalStateException("Only authenticated context could be serialized");
|
||||||
}
|
}
|
||||||
if (authentication.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareDetails) {
|
|
||||||
if (tenantAwareDetails.controller()) {
|
if (authentication.getPrincipal() instanceof Principal principal) {
|
||||||
throw new IllegalStateException("Controller authentication context is not supported");
|
tenant = principal.getTenant();
|
||||||
}
|
|
||||||
tenant = tenantAwareDetails.tenant();
|
|
||||||
} else if (authentication.getPrincipal() instanceof TenantAwareUser tenantAwareUser) {
|
|
||||||
tenant = tenantAwareUser.getTenant();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep the auditor, ofr audit purposes,
|
// keep the auditor, for audit purposes,
|
||||||
// sets principal to the resolved auditor and then deserialized authentication will return it as principal
|
// sets principal to the resolved auditor and then deserialized authentication will return it as principal
|
||||||
// since the class is not known to auditor aware - it shall used default - principal as auditor
|
// since the class is not known to auditor aware - it shall used default - principal as auditor
|
||||||
auditor = resolve(authentication);
|
auditor = resolve(authentication);
|
||||||
@@ -361,8 +345,7 @@ public class AccessContext {
|
|||||||
|
|
||||||
private SecurityContext toSecurityContext() {
|
private SecurityContext toSecurityContext() {
|
||||||
final SecurityContext ctx = SecurityContextHolder.createEmptyContext();
|
final SecurityContext ctx = SecurityContextHolder.createEmptyContext();
|
||||||
final Object details = tenant == null ? null : new TenantAwareAuthenticationDetails(tenant, false);
|
final Principal principal = new Principal(tenant, auditor);
|
||||||
final ActorAware principal = () -> auditor;
|
|
||||||
final Collection<? extends GrantedAuthority> grantedAuthorities = Stream.of(authorities).map(SimpleGrantedAuthority::new).toList();
|
final Collection<? extends GrantedAuthority> grantedAuthorities = Stream.of(authorities).map(SimpleGrantedAuthority::new).toList();
|
||||||
ctx.setAuthentication(new Authentication() {
|
ctx.setAuthentication(new Authentication() {
|
||||||
|
|
||||||
@@ -372,7 +355,7 @@ public class AccessContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
public @NonNull Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
return grantedAuthorities;
|
return grantedAuthorities;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +366,7 @@ public class AccessContext {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getDetails() {
|
public Object getDetails() {
|
||||||
return details;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -417,12 +400,10 @@ public class AccessContext {
|
|||||||
|
|
||||||
private static final List<SimpleGrantedAuthority> AUTHORITIES = List.of(new SimpleGrantedAuthority(SpRole.SYSTEM_ROLE));
|
private static final List<SimpleGrantedAuthority> AUTHORITIES = List.of(new SimpleGrantedAuthority(SpRole.SYSTEM_ROLE));
|
||||||
|
|
||||||
private final TenantAwareAuthenticationDetails details;
|
private final Principal principal;
|
||||||
private final TenantAwareUser principal;
|
|
||||||
|
|
||||||
private SystemCodeAuthentication(final String tenant) {
|
private SystemCodeAuthentication(final String tenant) {
|
||||||
details = new TenantAwareAuthenticationDetails(tenant, false);
|
principal = new Principal(tenant, SYSTEM_ACTOR);
|
||||||
principal = new TenantAwareUser(SYSTEM_ACTOR, SYSTEM_ACTOR, AUTHORITIES, tenant);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -431,7 +412,7 @@ public class AccessContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
public @NonNull Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
return AUTHORITIES;
|
return AUTHORITIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,7 +423,7 @@ public class AccessContext {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getDetails() {
|
public Object getDetails() {
|
||||||
return details;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -471,13 +452,11 @@ public class AccessContext {
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private final Authentication delegate;
|
private final Authentication delegate;
|
||||||
private final TenantAwareUser principal;
|
private final Principal principal;
|
||||||
private final TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails;
|
|
||||||
|
|
||||||
private AuthenticationDelegate(final String tenant, final String username, final Authentication delegate) {
|
private AuthenticationDelegate(final String tenant, final String username, final Authentication delegate) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
principal = new TenantAwareUser(username, username, delegate == null ? Collections.emptyList() : delegate.getAuthorities(), tenant);
|
principal = new Principal(tenant, username);
|
||||||
tenantAwareAuthenticationDetails = new TenantAwareAuthenticationDetails(tenant, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -489,8 +468,7 @@ public class AccessContext {
|
|||||||
public boolean equals(final Object another) {
|
public boolean equals(final Object another) {
|
||||||
if (another instanceof Authentication anotherAuthentication) {
|
if (another instanceof Authentication anotherAuthentication) {
|
||||||
return Objects.equals(delegate, anotherAuthentication) &&
|
return Objects.equals(delegate, anotherAuthentication) &&
|
||||||
Objects.equals(principal, anotherAuthentication.getPrincipal()) &&
|
Objects.equals(principal, anotherAuthentication.getPrincipal());
|
||||||
Objects.equals(tenantAwareAuthenticationDetails, anotherAuthentication.getDetails());
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -507,8 +485,8 @@ public class AccessContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
public @NonNull Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
return principal.getAuthorities();
|
return delegate.getAuthorities();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -518,7 +496,7 @@ public class AccessContext {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getDetails() {
|
public Object getDetails() {
|
||||||
return tenantAwareAuthenticationDetails;
|
return delegate.getDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import jakarta.servlet.http.HttpServletResponse;
|
|||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
|
||||||
import org.slf4j.MDC;
|
import org.slf4j.MDC;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -61,22 +60,14 @@ public class Mdc {
|
|||||||
return callable.call();
|
return callable.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
if (SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||||
if (authentication == null) {
|
|
||||||
return callable.call();
|
return callable.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
final String tenant;
|
final String tenant = AccessContext.tenant();
|
||||||
if (authentication.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails) {
|
|
||||||
tenant = tenantAwareAuthenticationDetails.tenant();
|
|
||||||
} else {
|
|
||||||
tenant = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String actor = Optional.ofNullable(AccessContext.actor())
|
final String actor = Optional.ofNullable(AccessContext.actor())
|
||||||
.filter(ctxActor -> !ctxActor.equals(AccessContext.SYSTEM_ACTOR)) // null and system are the same - system actor
|
.filter(ctxActor -> !ctxActor.equals(AccessContext.SYSTEM_ACTOR)) // null and system are the same - system actor
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
return asTenantAsActor0(tenant, actor, callable);
|
return asTenantAsActor0(tenant, actor, callable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.context;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.experimental.NonFinal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent an actor in the scope of the tenant
|
||||||
|
*/
|
||||||
|
@Value
|
||||||
|
@NonFinal
|
||||||
|
public class Principal implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
String tenant;
|
||||||
|
String actor;
|
||||||
|
}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made
|
|
||||||
* available under the terms of the Eclipse Public License 2.0
|
|
||||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.eclipse.hawkbit.tenancy;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An authentication details object {@link AbstractAuthenticationToken#getDetails()} which is stored in the
|
|
||||||
* spring security authentication token details to transport the principal and tenant in the security context session.
|
|
||||||
*/
|
|
||||||
public record TenantAwareAuthenticationDetails(String tenant, boolean controller) implements Serializable {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made
|
|
||||||
* available under the terms of the Eclipse Public License 2.0
|
|
||||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.eclipse.hawkbit.tenancy;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.core.userdetails.User;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A software provisioning user principal definition stored in the {@link SecurityContext} which contains the user specific attributes.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@ToString(callSuper = true)
|
|
||||||
public class TenantAwareUser extends User {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final String tenant;
|
|
||||||
|
|
||||||
public TenantAwareUser(
|
|
||||||
final String username, final String password, final Collection<? extends GrantedAuthority> authorities,
|
|
||||||
final String tenant) {
|
|
||||||
super(username, password, authorities == null ? Collections.emptyList() : authorities);
|
|
||||||
this.tenant = tenant;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAccountNonExpired() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAccountNonLocked() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCredentialsNonExpired() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,6 @@ import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.auth.SpRole;
|
import org.eclipse.hawkbit.auth.SpRole;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -66,7 +65,6 @@ class AccessContextAsSystemTest {
|
|||||||
assertThat(currentAuth.getClass().getSimpleName()).isEqualTo("SystemCodeAuthentication");
|
assertThat(currentAuth.getClass().getSimpleName()).isEqualTo("SystemCodeAuthentication");
|
||||||
assertThat(currentAuth.getCredentials()).isNull();
|
assertThat(currentAuth.getCredentials()).isNull();
|
||||||
assertThat(currentAuth.getAuthorities()).isEqualTo(List.of(new SimpleGrantedAuthority(SpRole.SYSTEM_ROLE)));
|
assertThat(currentAuth.getAuthorities()).isEqualTo(List.of(new SimpleGrantedAuthority(SpRole.SYSTEM_ROLE)));
|
||||||
assertThat(currentAuth.getDetails()).isEqualTo(new TenantAwareAuthenticationDetails("tenant", false));
|
|
||||||
});
|
});
|
||||||
SecurityContextHolder.clearContext();
|
SecurityContextHolder.clearContext();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import java.util.Set;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.auth.SpPermission;
|
import org.eclipse.hawkbit.auth.SpPermission;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -33,31 +32,28 @@ class SecurityContextSerializerTest {
|
|||||||
@Test
|
@Test
|
||||||
void testJsonSerialization() {
|
void testJsonSerialization() {
|
||||||
final SecurityContext securityContext = SecurityContextHolder.getContext();
|
final SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||||
|
final Principal principal = new Principal("my_tenant", "user");
|
||||||
final UsernamePasswordAuthenticationToken userPassAuthentication = new UsernamePasswordAuthenticationToken(
|
final UsernamePasswordAuthenticationToken userPassAuthentication = new UsernamePasswordAuthenticationToken(
|
||||||
"user", null, AUTHORITIES.stream().map(SimpleGrantedAuthority::new).toList());
|
principal, null, AUTHORITIES.stream().map(SimpleGrantedAuthority::new).toList());
|
||||||
final TenantAwareAuthenticationDetails details = new TenantAwareAuthenticationDetails("my_tenant", false);
|
|
||||||
userPassAuthentication.setDetails(details);
|
|
||||||
securityContext.setAuthentication(userPassAuthentication);
|
securityContext.setAuthentication(userPassAuthentication);
|
||||||
|
|
||||||
final String serialized = serialize(securityContext);
|
final String serialized = serialize(securityContext);
|
||||||
final SecurityContext deserialized = deserialize(serialized);
|
final SecurityContext deserialized = deserialize(serialized);
|
||||||
final Authentication authentication = deserialized.getAuthentication();
|
final Authentication authentication = deserialized.getAuthentication();
|
||||||
assertThat(resolve(authentication)).hasToString("user");
|
assertThat(resolve(authentication)).hasToString("user");
|
||||||
|
assertThat(authentication.getPrincipal()).isEqualTo(principal);
|
||||||
assertThat(authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()))
|
assertThat(authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()))
|
||||||
.isEqualTo(AUTHORITIES);
|
.isEqualTo(AUTHORITIES);
|
||||||
assertThat(authentication.isAuthenticated()).isTrue();
|
assertThat(authentication.isAuthenticated()).isTrue();
|
||||||
assertThat(authentication.getDetails()).isEqualTo(details);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testJsonSerializationSize() {
|
void testJsonSerializationSize() {
|
||||||
final SecurityContext securityContext = SecurityContextHolder.getContext();
|
final SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||||
final UsernamePasswordAuthenticationToken userPassAuthentication = new UsernamePasswordAuthenticationToken(
|
final UsernamePasswordAuthenticationToken userPassAuthentication = new UsernamePasswordAuthenticationToken(
|
||||||
"FirstName.FamilyName@domain1.domain0.com",
|
new Principal("my_test_enant", "FirstName.FamilyName@domain1.domain0.com"),
|
||||||
Map.of("should not be in" + bigString(10_000), "the output" + bigString(15_000)),
|
Map.of("should not be in" + bigString(10_000), "the output" + bigString(15_000)),
|
||||||
AUTHORITIES.stream().map(SimpleGrantedAuthority::new).toList());
|
AUTHORITIES.stream().map(SimpleGrantedAuthority::new).toList());
|
||||||
final TenantAwareAuthenticationDetails details = new TenantAwareAuthenticationDetails("my_test_enant", false);
|
|
||||||
userPassAuthentication.setDetails(details);
|
|
||||||
securityContext.setAuthentication(userPassAuthentication);
|
securityContext.setAuthentication(userPassAuthentication);
|
||||||
|
|
||||||
final String serialized = serialize(securityContext);
|
final String serialized = serialize(securityContext);
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
final Target findTargetByControllerID = targetManagement.getByControllerId(knownTargetControllerId);
|
final Target findTargetByControllerID = targetManagement.getByControllerId(knownTargetControllerId);
|
||||||
assertThat(findTargetByControllerID.getCreatedBy()).isEqualTo(knownCreatedBy);
|
assertThat(findTargetByControllerID.getCreatedBy()).isEqualTo(knownCreatedBy);
|
||||||
// make a poll, audit information should not be changed, run as controller principal!
|
// make a poll, audit information should not be changed, run as controller principal!
|
||||||
callAs(withController("controller", CONTROLLER_ROLE), () -> {
|
callAs(withController("controller"), () -> {
|
||||||
mvc.perform(get(CONTROLLER_BASE, AccessContext.tenant(), knownTargetControllerId))
|
mvc.perform(get(CONTROLLER_BASE, AccessContext.tenant(), knownTargetControllerId))
|
||||||
.andDo(MockMvcResultPrinter.print())
|
.andDo(MockMvcResultPrinter.print())
|
||||||
.andExpect(status().isOk());
|
.andExpect(status().isOk());
|
||||||
@@ -376,7 +376,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
final String knownControllerId1 = "0815";
|
final String knownControllerId1 = "0815";
|
||||||
final long create = System.currentTimeMillis();
|
final long create = System.currentTimeMillis();
|
||||||
// make a poll, audit information should be set on plug and play
|
// make a poll, audit information should be set on plug and play
|
||||||
callAs(withController("controller", CONTROLLER_ROLE), () -> {
|
callAs(withController("controller"), () -> {
|
||||||
mvc.perform(get(CONTROLLER_BASE, AccessContext.tenant(), knownControllerId1))
|
mvc.perform(get(CONTROLLER_BASE, AccessContext.tenant(), knownControllerId1))
|
||||||
.andDo(MockMvcResultPrinter.print())
|
.andDo(MockMvcResultPrinter.print())
|
||||||
.andExpect(status().isOk());
|
.andExpect(status().isOk());
|
||||||
@@ -385,9 +385,9 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
// verify
|
// verify
|
||||||
assertThat(targetManagement.getByControllerId(knownControllerId1)).satisfies(target -> {
|
assertThat(targetManagement.getByControllerId(knownControllerId1)).satisfies(target -> {
|
||||||
assertThat(target.getAddress()).isEqualTo(IpUtil.createHttpUri("127.0.0.1").toString());
|
assertThat(target.getAddress()).isEqualTo(IpUtil.createHttpUri("127.0.0.1").toString());
|
||||||
assertThat(target.getCreatedBy()).isEqualTo("CONTROLLER_PLUG_AND_PLAY");
|
assertThat(target.getCreatedBy()).isEqualTo(CONTROLLER_PLUG_AND_PLAY);
|
||||||
assertThat(target.getCreatedAt()).isGreaterThanOrEqualTo(create);
|
assertThat(target.getCreatedAt()).isGreaterThanOrEqualTo(create);
|
||||||
assertThat(target.getLastModifiedBy()).isEqualTo("CONTROLLER_PLUG_AND_PLAY");
|
assertThat(target.getLastModifiedBy()).isEqualTo(CONTROLLER_PLUG_AND_PLAY);
|
||||||
assertThat(target.getLastModifiedAt()).isGreaterThanOrEqualTo(create);
|
assertThat(target.getLastModifiedAt()).isGreaterThanOrEqualTo(create);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
# Logging START - activate to see request/response details
|
# Logging START - activate to see request/response details
|
||||||
|
logging.level.root=WARN
|
||||||
#logging.level.org.eclipse.hawkbit.rest.util.MockMvcResultPrinter=DEBUG
|
#logging.level.org.eclipse.hawkbit.rest.util.MockMvcResultPrinter=DEBUG
|
||||||
# Logging END
|
# Logging END
|
||||||
|
|
||||||
|
|||||||
@@ -12,19 +12,15 @@ package org.eclipse.hawkbit.security.controller;
|
|||||||
import static org.eclipse.hawkbit.context.AccessContext.asTenant;
|
import static org.eclipse.hawkbit.context.AccessContext.asTenant;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.eclipse.hawkbit.auth.SpRole;
|
import org.eclipse.hawkbit.auth.SpRole;
|
||||||
|
import org.eclipse.hawkbit.context.Principal;
|
||||||
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
|
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for Authentication mechanism.
|
* Interface for Authentication mechanism.
|
||||||
@@ -65,14 +61,11 @@ public interface Authenticator {
|
|||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private static final Collection<GrantedAuthority> CONTROLLER_AUTHORITY =
|
private final Principal principal;
|
||||||
List.of(new SimpleGrantedAuthority(SpRole.CONTROLLER_ROLE));
|
|
||||||
private final String controllerId;
|
|
||||||
|
|
||||||
AuthenticatedController(final String tenant, final String controllerId) {
|
AuthenticatedController(final String tenant, final String controllerId) {
|
||||||
super(CONTROLLER_AUTHORITY);
|
super(SpRole.CONTROLLER_AUTHORITIES);
|
||||||
super.setDetails(new TenantAwareAuthenticationDetails(tenant, true));
|
this.principal = new Principal(tenant, controllerId);
|
||||||
this.controllerId = controllerId;
|
|
||||||
setAuthenticated(true);
|
setAuthenticated(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +76,7 @@ public interface Authenticator {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getPrincipal() {
|
public Object getPrincipal() {
|
||||||
return controllerId;
|
return principal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationPrope
|
|||||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY;
|
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.context.Principal;
|
||||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
|
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
|
||||||
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
|
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
|
||||||
@@ -66,7 +67,7 @@ class GatewayTokenAuthenticatorTest {
|
|||||||
|
|
||||||
assertThat(authenticator.authenticate(securityToken))
|
assertThat(authenticator.authenticate(securityToken))
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.hasFieldOrPropertyWithValue("principal", CONTROLLER_ID);
|
.hasFieldOrPropertyWithValue("principal", new Principal("DEFAULT", CONTROLLER_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationPrope
|
|||||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_HEADER_ENABLED;
|
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_HEADER_ENABLED;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.context.Principal;
|
||||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
|
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
|
||||||
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
|
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
|
||||||
@@ -77,7 +78,7 @@ class SecurityHeaderAuthenticatorTest {
|
|||||||
|
|
||||||
assertThat(authenticator.authenticate(securityToken))
|
assertThat(authenticator.authenticate(securityToken))
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE);
|
.hasFieldOrPropertyWithValue("principal", new Principal("DEFAULT", CA_COMMON_NAME_VALUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,13 +93,13 @@ class SecurityHeaderAuthenticatorTest {
|
|||||||
|
|
||||||
assertThat(authenticator.authenticate(prepareSecurityToken(SINGLE_AUTHORITY)))
|
assertThat(authenticator.authenticate(prepareSecurityToken(SINGLE_AUTHORITY)))
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE);
|
.hasFieldOrPropertyWithValue("principal", new Principal("DEFAULT", CA_COMMON_NAME_VALUE));
|
||||||
assertThat(authenticator.authenticate(prepareSecurityToken(SECOND_AUTHORITY)))
|
assertThat(authenticator.authenticate(prepareSecurityToken(SECOND_AUTHORITY)))
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE);
|
.hasFieldOrPropertyWithValue("principal", new Principal("DEFAULT", CA_COMMON_NAME_VALUE));
|
||||||
assertThat(authenticator.authenticate(prepareSecurityToken(THIRD_AUTHORITY)))
|
assertThat(authenticator.authenticate(prepareSecurityToken(THIRD_AUTHORITY)))
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE);
|
.hasFieldOrPropertyWithValue("principal", new Principal("DEFAULT", CA_COMMON_NAME_VALUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.context.Principal;
|
||||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
|
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
|
||||||
@@ -72,7 +73,7 @@ class SecurityTokenAuthenticatorTest {
|
|||||||
|
|
||||||
assertThat(authenticator.authenticate(securityToken))
|
assertThat(authenticator.authenticate(securityToken))
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.hasFieldOrPropertyWithValue("principal", CONTROLLER_ID);
|
.hasFieldOrPropertyWithValue("principal", new Principal("DEFAULT", CONTROLLER_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import jakarta.validation.constraints.NotNull;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.audit.AuditLog;
|
import org.eclipse.hawkbit.audit.AuditLog;
|
||||||
import org.eclipse.hawkbit.auth.SpRole;
|
import org.eclipse.hawkbit.auth.SpRole;
|
||||||
|
import org.eclipse.hawkbit.context.Principal;
|
||||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||||
@@ -47,7 +48,6 @@ import org.eclipse.hawkbit.repository.model.ActionProperties;
|
|||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
|
||||||
import org.eclipse.hawkbit.utils.IpUtil;
|
import org.eclipse.hawkbit.utils.IpUtil;
|
||||||
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
|
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
|
||||||
import org.springframework.amqp.core.Message;
|
import org.springframework.amqp.core.Message;
|
||||||
@@ -170,11 +170,9 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void setTenantSecurityContext(final String tenant) {
|
private static void setTenantSecurityContext(final String tenant) {
|
||||||
final AnonymousAuthenticationToken authenticationToken = new AnonymousAuthenticationToken(
|
setSecurityContext(new AnonymousAuthenticationToken(
|
||||||
UUID.randomUUID().toString(), "AMQP-Controller",
|
UUID.randomUUID().toString(), new Principal(tenant, "AMQP-Controller"),
|
||||||
List.of(new SimpleGrantedAuthority(SpRole.CONTROLLER_ROLE)));
|
List.of(new SimpleGrantedAuthority(SpRole.CONTROLLER_ROLE))));
|
||||||
authenticationToken.setDetails(new TenantAwareAuthenticationDetails(tenant, true));
|
|
||||||
setSecurityContext(authenticationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isOptionalMessageBodyEmpty(final Message message) {
|
private static boolean isOptionalMessageBodyEmpty(final Message message) {
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ import org.springframework.util.CollectionUtils;
|
|||||||
abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpIntegrationTest {
|
abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpIntegrationTest {
|
||||||
|
|
||||||
protected static final String TENANT_EXIST = "DEFAULT";
|
protected static final String TENANT_EXIST = "DEFAULT";
|
||||||
protected static final String CREATED_BY = "CONTROLLER_PLUG_AND_PLAY";
|
|
||||||
protected static final String CORRELATION_ID = UUID.randomUUID().toString();
|
protected static final String CORRELATION_ID = UUID.randomUUID().toString();
|
||||||
|
|
||||||
protected ReplyToListener replyToListener;
|
protected ReplyToListener replyToListener;
|
||||||
@@ -132,7 +131,7 @@ abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpIntegratio
|
|||||||
protected DistributionSetAssignmentResult registerTargetAndAssignDistributionSet(final Long assignDs,
|
protected DistributionSetAssignmentResult registerTargetAndAssignDistributionSet(final Long assignDs,
|
||||||
final TargetUpdateStatus expectedStatus, final Set<SoftwareModule> expectedSoftwareModulesInMessage,
|
final TargetUpdateStatus expectedStatus, final Set<SoftwareModule> expectedSoftwareModulesInMessage,
|
||||||
final String controllerId) {
|
final String controllerId) {
|
||||||
registerAndAssertTargetWithExistingTenant(controllerId, 1, expectedStatus, CREATED_BY);
|
registerAndAssertTargetWithExistingTenant(controllerId, 1, expectedStatus, CONTROLLER_PLUG_AND_PLAY);
|
||||||
|
|
||||||
final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(assignDs, controllerId);
|
final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(assignDs, controllerId);
|
||||||
if (isConfirmationFlowEnabled()) {
|
if (isConfirmationFlowEnabled()) {
|
||||||
@@ -253,7 +252,7 @@ abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpIntegratio
|
|||||||
protected void registerAndAssertTargetWithExistingTenant(final String controllerId,
|
protected void registerAndAssertTargetWithExistingTenant(final String controllerId,
|
||||||
final int existingTargetsAfterCreation) {
|
final int existingTargetsAfterCreation) {
|
||||||
registerAndAssertTargetWithExistingTenant(controllerId, existingTargetsAfterCreation,
|
registerAndAssertTargetWithExistingTenant(controllerId, existingTargetsAfterCreation,
|
||||||
TargetUpdateStatus.REGISTERED, CREATED_BY);
|
TargetUpdateStatus.REGISTERED, CONTROLLER_PLUG_AND_PLAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void registerAndAssertTargetWithExistingTenant(final String controllerId,
|
protected void registerAndAssertTargetWithExistingTenant(final String controllerId,
|
||||||
@@ -282,7 +281,7 @@ abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpIntegratio
|
|||||||
final Map<String, String> attributes) {
|
final Map<String, String> attributes) {
|
||||||
final int version = controllerManagement.findByControllerId(controllerId).get().getOptLockRevision();
|
final int version = controllerManagement.findByControllerId(controllerId).get().getOptLockRevision();
|
||||||
registerAndAssertTargetWithExistingTenant(controllerId, name, existingTargetsAfterCreation,
|
registerAndAssertTargetWithExistingTenant(controllerId, name, existingTargetsAfterCreation,
|
||||||
expectedTargetStatus, CREATED_BY, attributes, () -> findTargetBasedOnNewVersion(controllerId, version));
|
expectedTargetStatus, CONTROLLER_PLUG_AND_PLAY, attributes, () -> findTargetBasedOnNewVersion(controllerId, version));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Message createTargetMessage(final String controllerId, final String tenant) {
|
protected Message createTargetMessage(final String controllerId, final String tenant) {
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
|
|||||||
void registerTargetWithName() {
|
void registerTargetWithName() {
|
||||||
final String controllerId = TARGET_PREFIX + "registerTargetWithName";
|
final String controllerId = TARGET_PREFIX + "registerTargetWithName";
|
||||||
final String name = "NonDefaultTargetName";
|
final String name = "NonDefaultTargetName";
|
||||||
registerAndAssertTargetWithExistingTenant(controllerId, name, 1, TargetUpdateStatus.REGISTERED, CREATED_BY, null);
|
registerAndAssertTargetWithExistingTenant(controllerId, name, 1, TargetUpdateStatus.REGISTERED, CONTROLLER_PLUG_AND_PLAY, null);
|
||||||
registerSameTargetAndAssertBasedOnVersion(controllerId, name + "_updated", 1, TargetUpdateStatus.REGISTERED, null);
|
registerSameTargetAndAssertBasedOnVersion(controllerId, name + "_updated", 1, TargetUpdateStatus.REGISTERED, null);
|
||||||
|
|
||||||
Mockito.verifyNoInteractions(getDeadletterListener());
|
Mockito.verifyNoInteractions(getDeadletterListener());
|
||||||
@@ -153,7 +153,7 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
|
|||||||
attributes.put("testKey1", "testValue1");
|
attributes.put("testKey1", "testValue1");
|
||||||
attributes.put("testKey2", "testValue2");
|
attributes.put("testKey2", "testValue2");
|
||||||
|
|
||||||
registerAndAssertTargetWithExistingTenant(controllerId, null, 1, TargetUpdateStatus.REGISTERED, CREATED_BY, attributes);
|
registerAndAssertTargetWithExistingTenant(controllerId, null, 1, TargetUpdateStatus.REGISTERED, CONTROLLER_PLUG_AND_PLAY, attributes);
|
||||||
|
|
||||||
attributes.put("testKey3", "testValue3");
|
attributes.put("testKey3", "testValue3");
|
||||||
registerSameTargetAndAssertBasedOnVersion(controllerId, null, 1, TargetUpdateStatus.REGISTERED, attributes);
|
registerSameTargetAndAssertBasedOnVersion(controllerId, null, 1, TargetUpdateStatus.REGISTERED, attributes);
|
||||||
@@ -176,7 +176,7 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
|
|||||||
final Map<String, String> attributes = new HashMap<>();
|
final Map<String, String> attributes = new HashMap<>();
|
||||||
attributes.put("testKey1", "testValue1");
|
attributes.put("testKey1", "testValue1");
|
||||||
attributes.put("testKey2", "testValue2");
|
attributes.put("testKey2", "testValue2");
|
||||||
registerAndAssertTargetWithExistingTenant(controllerId, name, 1, TargetUpdateStatus.REGISTERED, CREATED_BY, attributes);
|
registerAndAssertTargetWithExistingTenant(controllerId, name, 1, TargetUpdateStatus.REGISTERED, CONTROLLER_PLUG_AND_PLAY, attributes);
|
||||||
|
|
||||||
attributes.put("testKey3", "testValue3");
|
attributes.put("testKey3", "testValue3");
|
||||||
registerSameTargetAndAssertBasedOnVersion(controllerId, name + "_updated", 1, TargetUpdateStatus.REGISTERED, attributes);
|
registerSameTargetAndAssertBasedOnVersion(controllerId, name + "_updated", 1, TargetUpdateStatus.REGISTERED, attributes);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
# Logging START - activate to see request/response details
|
# Logging START - activate to see request/response details
|
||||||
|
logging.level.root=WARN
|
||||||
#logging.level.org.eclipse.hawkbit.rest.util.MockMvcResultPrinter=DEBUG
|
#logging.level.org.eclipse.hawkbit.rest.util.MockMvcResultPrinter=DEBUG
|
||||||
# Logging END
|
# Logging END
|
||||||
hawkbit.events.remote.enabled=false
|
hawkbit.events.remote.enabled=false
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import java.util.Optional;
|
|||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.auth.SpPermission;
|
|
||||||
import org.eclipse.hawkbit.context.Mdc;
|
import org.eclipse.hawkbit.context.Mdc;
|
||||||
|
import org.eclipse.hawkbit.context.Principal;
|
||||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||||
import org.eclipse.hawkbit.oidc.OidcProperties;
|
import org.eclipse.hawkbit.oidc.OidcProperties;
|
||||||
import org.eclipse.hawkbit.oidc.OidcProperties.Oauth2.ResourceServer.Jwt.Claim;
|
import org.eclipse.hawkbit.oidc.OidcProperties.Oauth2.ResourceServer.Jwt.Claim;
|
||||||
@@ -30,8 +30,6 @@ import org.eclipse.hawkbit.repository.SystemManagement;
|
|||||||
import org.eclipse.hawkbit.rest.SecurityManagedConfiguration;
|
import org.eclipse.hawkbit.rest.SecurityManagedConfiguration;
|
||||||
import org.eclipse.hawkbit.rest.security.DosFilter;
|
import org.eclipse.hawkbit.rest.security.DosFilter;
|
||||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareUser;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
@@ -84,9 +82,7 @@ public class MgmtSecurityConfiguration {
|
|||||||
public FilterRegistrationBean<DosFilter> dosFilterREST() {
|
public FilterRegistrationBean<DosFilter> dosFilterREST() {
|
||||||
final FilterRegistrationBean<DosFilter> filterRegBean = SecurityManagedConfiguration.dosFilter(null,
|
final FilterRegistrationBean<DosFilter> filterRegBean = SecurityManagedConfiguration.dosFilter(null,
|
||||||
securityProperties.getDos().getFilter(), securityProperties.getClients());
|
securityProperties.getDos().getFilter(), securityProperties.getClients());
|
||||||
filterRegBean.setUrlPatterns(List.of(
|
filterRegBean.setUrlPatterns(List.of(MgmtRestConstants.REST + "/*", "/system/admin/*"));
|
||||||
MgmtRestConstants.REST + "/*",
|
|
||||||
"/system/admin/*"));
|
|
||||||
filterRegBean.setOrder(SecurityManagedConfiguration.DOS_FILTER_ORDER);
|
filterRegBean.setOrder(SecurityManagedConfiguration.DOS_FILTER_ORDER);
|
||||||
filterRegBean.setName("dosMgmtFilter");
|
filterRegBean.setName("dosMgmtFilter");
|
||||||
|
|
||||||
@@ -179,7 +175,7 @@ public class MgmtSecurityConfiguration {
|
|||||||
.map(GrantedAuthority.class::cast)
|
.map(GrantedAuthority.class::cast)
|
||||||
.toList())
|
.toList())
|
||||||
.orElseGet(Collections::emptyList);
|
.orElseGet(Collections::emptyList);
|
||||||
return new HawkbitJwtAuthenticationToken(jwt, new TenantAwareUser(username, username, authorities, tenant), authorities);
|
return new HawkbitJwtAuthenticationToken(jwt, new Principal(tenant, username), authorities);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,9 +214,8 @@ public class MgmtSecurityConfiguration {
|
|||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
private HawkbitJwtAuthenticationToken(final Jwt jwt, TenantAwareUser user, final Collection<GrantedAuthority> authorities) {
|
private HawkbitJwtAuthenticationToken(final Jwt jwt, Principal principal, final Collection<GrantedAuthority> authorities) {
|
||||||
super(jwt, user, jwt, authorities);
|
super(jwt, principal, jwt, authorities);
|
||||||
setDetails(new TenantAwareAuthenticationDetails(user.getTenant(), false));
|
|
||||||
name = jwt.getSubject();
|
name = jwt.getSubject();
|
||||||
setAuthenticated(true);
|
setAuthenticated(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,16 +26,14 @@ import lombok.AccessLevel;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import org.eclipse.hawkbit.auth.SpRole;
|
||||||
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
||||||
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
|
||||||
import org.springframework.data.annotation.CreatedBy;
|
import org.springframework.data.annotation.CreatedBy;
|
||||||
import org.springframework.data.annotation.CreatedDate;
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
import org.springframework.data.annotation.LastModifiedBy;
|
import org.springframework.data.annotation.LastModifiedBy;
|
||||||
import org.springframework.data.annotation.LastModifiedDate;
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base hawkBit entity class containing the common attributes for EclipseLink.
|
* Base hawkBit entity class containing the common attributes for EclipseLink.
|
||||||
@@ -95,7 +93,7 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
|||||||
|
|
||||||
@LastModifiedBy
|
@LastModifiedBy
|
||||||
public void setLastModifiedBy(final String lastModifiedBy) {
|
public void setLastModifiedBy(final String lastModifiedBy) {
|
||||||
if (this.lastModifiedBy != null && isController()) {
|
if (this.lastModifiedBy != null && SpRole.isController()) {
|
||||||
// initialized and controller = doesn't update
|
// initialized and controller = doesn't update
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -110,7 +108,7 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
|||||||
|
|
||||||
@LastModifiedDate
|
@LastModifiedDate
|
||||||
public void setLastModifiedAt(final long lastModifiedAt) {
|
public void setLastModifiedAt(final long lastModifiedAt) {
|
||||||
if (this.lastModifiedAt != 0 && isController()) {
|
if (this.lastModifiedAt != 0 && SpRole.isController()) {
|
||||||
// initialized and controller = doesn't update
|
// initialized and controller = doesn't update
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -197,11 +195,4 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
|||||||
// fire events onl AFTER transaction commit
|
// fire events onl AFTER transaction commit
|
||||||
AfterTransactionCommitExecutor.afterCommit(runnable);
|
AfterTransactionCommitExecutor.afterCommit(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isController() {
|
|
||||||
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
return authentication != null
|
|
||||||
&& authentication.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareDetails
|
|
||||||
&& tenantAwareDetails.controller();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -86,9 +86,7 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
|
|||||||
final String currentTenant = AccessContext.tenant();
|
final String currentTenant = AccessContext.tenant();
|
||||||
if (currentTenant == null) {
|
if (currentTenant == null) {
|
||||||
throw new TenantNotExistException(
|
throw new TenantNotExistException(
|
||||||
String.format(
|
String.format("Tenant not found in the context, cannot create entity %s with id %d", getClass(), getId()));
|
||||||
"AccessContext %s does not exists, cannot create entity %s with id %d",
|
|
||||||
AccessContext.tenant(), getClass(), getId()));
|
|
||||||
}
|
}
|
||||||
setTenant(currentTenant.toUpperCase());
|
setTenant(currentTenant.toUpperCase());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.repository.jpa.model;
|
package org.eclipse.hawkbit.repository.jpa.model;
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
|
|
||||||
import jakarta.persistence.Access;
|
import jakarta.persistence.Access;
|
||||||
import jakarta.persistence.AccessType;
|
import jakarta.persistence.AccessType;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
@@ -28,16 +26,14 @@ import lombok.AccessLevel;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import org.eclipse.hawkbit.auth.SpRole;
|
||||||
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
||||||
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
|
||||||
import org.springframework.data.annotation.CreatedBy;
|
import org.springframework.data.annotation.CreatedBy;
|
||||||
import org.springframework.data.annotation.CreatedDate;
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
import org.springframework.data.annotation.LastModifiedBy;
|
import org.springframework.data.annotation.LastModifiedBy;
|
||||||
import org.springframework.data.annotation.LastModifiedDate;
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base hawkBit entity class containing the common attributes for Hibernate.
|
* Base hawkBit entity class containing the common attributes for Hibernate.
|
||||||
@@ -51,9 +47,6 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
|||||||
|
|
||||||
protected static final int USERNAME_FIELD_LENGTH = 64;
|
protected static final int USERNAME_FIELD_LENGTH = 64;
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
@Setter // should be used just for test purposes
|
@Setter // should be used just for test purposes
|
||||||
@Getter
|
@Getter
|
||||||
@Id
|
@Id
|
||||||
@@ -98,7 +91,7 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
|||||||
|
|
||||||
@LastModifiedBy
|
@LastModifiedBy
|
||||||
public void setLastModifiedBy(final String lastModifiedBy) {
|
public void setLastModifiedBy(final String lastModifiedBy) {
|
||||||
if (this.lastModifiedBy != null && isController()) {
|
if (this.lastModifiedBy != null && SpRole.isController()) {
|
||||||
// initialized and controller = doesn't update
|
// initialized and controller = doesn't update
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -114,7 +107,7 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
|||||||
|
|
||||||
@LastModifiedDate
|
@LastModifiedDate
|
||||||
public void setLastModifiedAt(final long lastModifiedAt) {
|
public void setLastModifiedAt(final long lastModifiedAt) {
|
||||||
if (this.lastModifiedAt != 0 && isController()) {
|
if (this.lastModifiedAt != 0 && SpRole.isController()) {
|
||||||
// initialized and controller = doesn't update
|
// initialized and controller = doesn't update
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -202,11 +195,4 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
|||||||
// fire events onl AFTER transaction commit
|
// fire events onl AFTER transaction commit
|
||||||
AfterTransactionCommitExecutor.afterCommit(runnable);
|
AfterTransactionCommitExecutor.afterCommit(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isController() {
|
|
||||||
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
return authentication != null
|
|
||||||
&& authentication.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareDetails
|
|
||||||
&& tenantAwareDetails.controller();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.repository.jpa.model;
|
package org.eclipse.hawkbit.repository.jpa.model;
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
@@ -36,9 +35,6 @@ import org.hibernate.annotations.TenantId;
|
|||||||
@MappedSuperclass
|
@MappedSuperclass
|
||||||
public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEntity implements TenantAwareBaseEntity {
|
public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEntity implements TenantAwareBaseEntity {
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
@Column(name = "tenant", nullable = false, insertable = true, updatable = false, length = 40)
|
@Column(name = "tenant", nullable = false, insertable = true, updatable = false, length = 40)
|
||||||
@Size(min = 1, max = 40)
|
@Size(min = 1, max = 40)
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -86,9 +82,7 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
|
|||||||
final String currentTenant = AccessContext.tenant();
|
final String currentTenant = AccessContext.tenant();
|
||||||
if (currentTenant == null) {
|
if (currentTenant == null) {
|
||||||
throw new TenantNotExistException(
|
throw new TenantNotExistException(
|
||||||
String.format(
|
String.format("Tenant not found in the context, cannot create entity %s with id %d", getClass(), getId()));
|
||||||
"AccessContext %s does not exists, cannot create entity %s with id %d",
|
|
||||||
AccessContext.tenant(), getClass(), getId()));
|
|
||||||
}
|
}
|
||||||
setTenant(currentTenant.toUpperCase());
|
setTenant(currentTenant.toUpperCase());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import org.springframework.test.context.ContextConfiguration;
|
|||||||
"hawkbit.rsql.caseInsensitiveDB=true",
|
"hawkbit.rsql.caseInsensitiveDB=true",
|
||||||
"spring.main.allow-bean-definition-overriding=true",
|
"spring.main.allow-bean-definition-overriding=true",
|
||||||
"spring.main.banner-mode=off",
|
"spring.main.banner-mode=off",
|
||||||
"logging.level.root=ERROR" })
|
"logging.level.root=WARN" })
|
||||||
@ContextConfiguration(classes = { JpaRepositoryConfiguration.class, TestConfiguration.class })
|
@ContextConfiguration(classes = { JpaRepositoryConfiguration.class, TestConfiguration.class })
|
||||||
@Disabled("For manual run only, while playing around with RSQL to SQL")
|
@Disabled("For manual run only, while playing around with RSQL to SQL")
|
||||||
@SuppressWarnings("java:S2699") // java:S2699 - manual test, don't actually does assertions
|
@SuppressWarnings("java:S2699") // java:S2699 - manual test, don't actually does assertions
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ import java.util.function.Supplier;
|
|||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.eclipse.hawkbit.auth.SpPermission;
|
import org.eclipse.hawkbit.auth.SpPermission;
|
||||||
import org.eclipse.hawkbit.context.AccessContext;
|
import org.eclipse.hawkbit.context.AccessContext;
|
||||||
|
import org.eclipse.hawkbit.context.Principal;
|
||||||
import org.eclipse.hawkbit.repository.AutoAssignHandler;
|
import org.eclipse.hawkbit.repository.AutoAssignHandler;
|
||||||
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
||||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.MockedStatic;
|
import org.mockito.MockedStatic;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
@@ -129,9 +129,7 @@ class SecurityContextCtxTest extends AbstractJpaIntegrationTest {
|
|||||||
private static SecurityContext createUserContext(final int testId) {
|
private static SecurityContext createUserContext(final int testId) {
|
||||||
final SecurityContext userContext = SecurityContextHolder.createEmptyContext();
|
final SecurityContext userContext = SecurityContextHolder.createEmptyContext();
|
||||||
final UsernamePasswordAuthenticationToken userPassAuthentication = new UsernamePasswordAuthenticationToken(
|
final UsernamePasswordAuthenticationToken userPassAuthentication = new UsernamePasswordAuthenticationToken(
|
||||||
"user", null, AUTHORITIES.stream().map(SimpleGrantedAuthority::new).toList());
|
new Principal("my_tenant_" + testId, "user"), null, AUTHORITIES.stream().map(SimpleGrantedAuthority::new).toList());
|
||||||
final TenantAwareAuthenticationDetails details = new TenantAwareAuthenticationDetails("my_tenant_" + testId, false);
|
|
||||||
userPassAuthentication.setDetails(details);
|
|
||||||
userContext.setAuthentication(userPassAuthentication);
|
userContext.setAuthentication(userPassAuthentication);
|
||||||
|
|
||||||
assertThat(userContext).isNotNull();
|
assertThat(userContext).isNotNull();
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import jakarta.validation.ConstraintViolationException;
|
|||||||
|
|
||||||
import org.assertj.core.api.Assertions;
|
import org.assertj.core.api.Assertions;
|
||||||
import org.eclipse.hawkbit.auth.SpPermission;
|
import org.eclipse.hawkbit.auth.SpPermission;
|
||||||
import org.eclipse.hawkbit.ql.jpa.SpecificationBuilder;
|
|
||||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||||
import org.eclipse.hawkbit.repository.TargetTypeManagement;
|
import org.eclipse.hawkbit.repository.TargetTypeManagement;
|
||||||
import org.eclipse.hawkbit.repository.UpdateMode;
|
import org.eclipse.hawkbit.repository.UpdateMode;
|
||||||
@@ -108,7 +107,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
final int allowedAttributes = quotaManagement.getMaxAttributeEntriesPerTarget();
|
final int allowedAttributes = quotaManagement.getMaxAttributeEntriesPerTarget();
|
||||||
testdataFactory.createTarget(controllerId);
|
testdataFactory.createTarget(controllerId);
|
||||||
|
|
||||||
final WithUser withController = SecurityContextSwitch.withController("controller", CONTROLLER_ROLE);
|
final WithUser withController = SecurityContextSwitch.withController("controller");
|
||||||
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
||||||
.isThrownBy(() -> runAs(withController, () -> writeAttributes(controllerId, allowedAttributes + 1, "key", "value")))
|
.isThrownBy(() -> runAs(withController, () -> writeAttributes(controllerId, allowedAttributes + 1, "key", "value")))
|
||||||
.withMessageContaining("" + allowedAttributes);
|
.withMessageContaining("" + allowedAttributes);
|
||||||
@@ -184,7 +183,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
final Long actionId = createTargetAndAssignDs();
|
final Long actionId = createTargetAndAssignDs();
|
||||||
|
|
||||||
SecurityContextSwitch
|
SecurityContextSwitch
|
||||||
.getAs(SecurityContextSwitch.withController("controller", CONTROLLER_ROLE), () -> {
|
.getAs(SecurityContextSwitch.withController("controller"), () -> {
|
||||||
// Fails as one entry is already in there from the assignment
|
// Fails as one entry is already in there from the assignment
|
||||||
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
||||||
.isThrownBy(() -> writeStatus(actionId, allowStatusEntries))
|
.isThrownBy(() -> writeStatus(actionId, allowStatusEntries))
|
||||||
@@ -243,7 +242,8 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
assertThat(actionId1).isNotNull();
|
assertThat(actionId1).isNotNull();
|
||||||
final ActionStatusCreateBuilder status = ActionStatusCreate.builder().actionId(actionId1).status(Status.WARNING);
|
final ActionStatusCreateBuilder status = ActionStatusCreate.builder().actionId(actionId1).status(Status.WARNING);
|
||||||
for (int i = 0; i < maxStatusEntries; i++) {
|
for (int i = 0; i < maxStatusEntries; i++) {
|
||||||
controllerManagement.addInformationalActionStatus(status.messages(List.of("Msg " + i)).timestamp(System.currentTimeMillis()).build());
|
controllerManagement.addInformationalActionStatus(
|
||||||
|
status.messages(List.of("Msg " + i)).timestamp(System.currentTimeMillis()).build());
|
||||||
}
|
}
|
||||||
final ActionStatusCreate actionStatusCreate = status.build();
|
final ActionStatusCreate actionStatusCreate = status.build();
|
||||||
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
||||||
@@ -255,7 +255,8 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
assertThat(actionId2).isNotEqualTo(actionId1);
|
assertThat(actionId2).isNotEqualTo(actionId1);
|
||||||
final ActionStatusCreateBuilder statusWarning = ActionStatusCreate.builder().actionId(actionId2).status(Status.WARNING);
|
final ActionStatusCreateBuilder statusWarning = ActionStatusCreate.builder().actionId(actionId2).status(Status.WARNING);
|
||||||
for (int i = 0; i < maxStatusEntries; i++) {
|
for (int i = 0; i < maxStatusEntries; i++) {
|
||||||
controllerManagement.addUpdateActionStatus(statusWarning.messages(List.of("Msg " + i)).timestamp(System.currentTimeMillis()).build());
|
controllerManagement.addUpdateActionStatus(
|
||||||
|
statusWarning.messages(List.of("Msg " + i)).timestamp(System.currentTimeMillis()).build());
|
||||||
}
|
}
|
||||||
final ActionStatusCreate actionStatusCreateQE = statusWarning.build();
|
final ActionStatusCreate actionStatusCreateQE = statusWarning.build();
|
||||||
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
||||||
@@ -867,7 +868,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
.as("Expected an ConcurrencyFailureException to be thrown!")
|
.as("Expected an ConcurrencyFailureException to be thrown!")
|
||||||
.isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST));
|
.isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST));
|
||||||
|
|
||||||
verify(mockTargetRepository, times(10 /* default retry max */+ 1)).findOne(any(Specification.class));
|
verify(mockTargetRepository, times(10 /* default retry max */ + 1)).findOne(any(Specification.class));
|
||||||
} finally {
|
} finally {
|
||||||
// revert
|
// revert
|
||||||
((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository);
|
((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository);
|
||||||
@@ -1161,7 +1162,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
final String controllerId = "test123";
|
final String controllerId = "test123";
|
||||||
final Target target = testdataFactory.createTarget(controllerId);
|
final Target target = testdataFactory.createTarget(controllerId);
|
||||||
|
|
||||||
SecurityContextSwitch.getAs(SecurityContextSwitch.withController("controller", CONTROLLER_ROLE, SpPermission.READ_TARGET), () -> {
|
SecurityContextSwitch.getAs(SecurityContextSwitch.withController("controller"), () -> {
|
||||||
addAttributeAndVerify(controllerId);
|
addAttributeAndVerify(controllerId);
|
||||||
addSecondAttributeAndVerify(controllerId);
|
addSecondAttributeAndVerify(controllerId);
|
||||||
updateAttributeAndVerify(controllerId);
|
updateAttributeAndVerify(controllerId);
|
||||||
@@ -1769,7 +1770,8 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
testData.put("test1", "testdata1");
|
testData.put("test1", "testdata1");
|
||||||
controllerManagement.updateControllerAttributes(controllerId, testData, null);
|
controllerManagement.updateControllerAttributes(controllerId, testData, null);
|
||||||
|
|
||||||
assertThat(targetManagement.getControllerAttributes(controllerId)).as("Controller Attributes are wrong").isEqualTo(testData);
|
SecurityContextSwitch.getAs(SecurityContextSwitch.withUser("bumlux", SpPermission.READ_TARGET), () ->
|
||||||
|
assertThat(targetManagement.getControllerAttributes(controllerId)).as("Controller Attributes are wrong").isEqualTo(testData));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSecondAttributeAndVerify(final String controllerId) {
|
private void addSecondAttributeAndVerify(final String controllerId) {
|
||||||
@@ -1778,8 +1780,8 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
controllerManagement.updateControllerAttributes(controllerId, testData, null);
|
controllerManagement.updateControllerAttributes(controllerId, testData, null);
|
||||||
|
|
||||||
testData.put("test1", "testdata1");
|
testData.put("test1", "testdata1");
|
||||||
assertThat(targetManagement.getControllerAttributes(controllerId)).as("Controller Attributes are wrong")
|
SecurityContextSwitch.getAs(SecurityContextSwitch.withUser("bumlux", SpPermission.READ_TARGET), () ->
|
||||||
.isEqualTo(testData);
|
assertThat(targetManagement.getControllerAttributes(controllerId)).as("Controller Attributes are wrong").isEqualTo(testData));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAttributeAndVerify(final String controllerId) {
|
private void updateAttributeAndVerify(final String controllerId) {
|
||||||
@@ -1789,8 +1791,8 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
controllerManagement.updateControllerAttributes(controllerId, testData, null);
|
controllerManagement.updateControllerAttributes(controllerId, testData, null);
|
||||||
|
|
||||||
testData.put("test2", "testdata20");
|
testData.put("test2", "testdata20");
|
||||||
assertThat(targetManagement.getControllerAttributes(controllerId)).as("Controller Attributes are wrong")
|
SecurityContextSwitch.getAs(SecurityContextSwitch.withUser("bumlux", SpPermission.READ_TARGET), () ->
|
||||||
.isEqualTo(testData);
|
assertThat(targetManagement.getControllerAttributes(controllerId)).as("Controller Attributes are wrong").isEqualTo(testData));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTargetAttributesWithUpdateModeRemove(final String controllerId) {
|
private void updateTargetAttributesWithUpdateModeRemove(final String controllerId) {
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ class ManagementSecurityTest extends AbstractJpaIntegrationTest {
|
|||||||
} catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e1) {
|
} catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e1) {
|
||||||
log.debug("{} is not a builder. Throws could not instantiate", clazz.getName());
|
log.debug("{} is not a builder. Throws could not instantiate", clazz.getName());
|
||||||
}
|
}
|
||||||
log.error("Could not instantiate {}", clazz.getName(), e);
|
log.debug("Could not instantiate {} (but if inside instance, could be fine)", clazz.getName(), e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,12 +401,12 @@ class ManagementSecurityTest extends AbstractJpaIntegrationTest {
|
|||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
protected void assertPermissionsCheck(final Method managementInterfaceMethod, final Object managementObject, final String... permissions) {
|
protected void assertPermissionsCheck(final Method managementInterfaceMethod, final Object managementObject, final String... permissions) {
|
||||||
final Callable<?> callable = () -> {
|
|
||||||
try {
|
|
||||||
final Object[] params = new Object[managementInterfaceMethod.getParameterCount()];
|
final Object[] params = new Object[managementInterfaceMethod.getParameterCount()];
|
||||||
for (int i = 0; i < params.length; i++) {
|
for (int i = 0; i < params.length; i++) {
|
||||||
params[i] = instance(managementInterfaceMethod.getParameterTypes()[i]);
|
params[i] = instance(managementInterfaceMethod.getParameterTypes()[i]);
|
||||||
}
|
}
|
||||||
|
final Callable<?> callable = () -> {
|
||||||
|
try {
|
||||||
return managementInterfaceMethod.invoke(managementObject, params);
|
return managementInterfaceMethod.invoke(managementObject, params);
|
||||||
} catch (final InvocationTargetException e) {
|
} catch (final InvocationTargetException e) {
|
||||||
if (e.getCause() instanceof RuntimeException re) {
|
if (e.getCause() instanceof RuntimeException re) {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
# SPDX-License-Identifier: EPL-2.0
|
# SPDX-License-Identifier: EPL-2.0
|
||||||
#
|
#
|
||||||
|
|
||||||
|
logging.level.root=WARN
|
||||||
|
# could be used to suppress some logging if the root level is set to INFO or DEBUG, e.g. when debugging a test case
|
||||||
NOISE_SUPPRESS_LEVEL=WARN
|
NOISE_SUPPRESS_LEVEL=WARN
|
||||||
|
|
||||||
### General logging configuration - START
|
### General logging configuration - START
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import org.eclipse.hawkbit.artifact.fs.FileArtifactStorage;
|
|||||||
import org.eclipse.hawkbit.artifact.urlresolver.PropertyBasedArtifactUrlResolver;
|
import org.eclipse.hawkbit.artifact.urlresolver.PropertyBasedArtifactUrlResolver;
|
||||||
import org.eclipse.hawkbit.artifact.urlresolver.PropertyBasedArtifactUrlResolverProperties;
|
import org.eclipse.hawkbit.artifact.urlresolver.PropertyBasedArtifactUrlResolverProperties;
|
||||||
import org.eclipse.hawkbit.auth.Hierarchy;
|
import org.eclipse.hawkbit.auth.Hierarchy;
|
||||||
|
import org.eclipse.hawkbit.auth.SpRole;
|
||||||
import org.eclipse.hawkbit.context.AccessContext;
|
import org.eclipse.hawkbit.context.AccessContext;
|
||||||
import org.eclipse.hawkbit.repository.RepositoryConfiguration;
|
import org.eclipse.hawkbit.repository.RepositoryConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||||
@@ -138,7 +139,7 @@ public class TestConfiguration implements AsyncConfigurer {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
AuditorAware<String> auditorAware() {
|
AuditorAware<String> auditorAware() {
|
||||||
return () -> Optional.ofNullable(AccessContext.actor());
|
return () -> Optional.ofNullable(SpRole.isController() ? "CONTROLLER_PLUG_AND_PLAY" : AccessContext.actor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ public abstract class AbstractIntegrationTest {
|
|||||||
protected static final Pageable PAGE = PageRequest.of(0, 500, Sort.by(Direction.ASC, "id"));
|
protected static final Pageable PAGE = PageRequest.of(0, 500, Sort.by(Direction.ASC, "id"));
|
||||||
protected static final Pageable UNPAGED = Pageable.unpaged();
|
protected static final Pageable UNPAGED = Pageable.unpaged();
|
||||||
|
|
||||||
|
protected static final String CONTROLLER_PLUG_AND_PLAY = "CONTROLLER_PLUG_AND_PLAY";
|
||||||
protected static final URI LOCALHOST = URI.create("http://127.0.0.1");
|
protected static final URI LOCALHOST = URI.create("http://127.0.0.1");
|
||||||
protected static final Random RND = TestdataFactory.RND;
|
protected static final Random RND = TestdataFactory.RND;
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,8 @@ import java.util.function.Supplier;
|
|||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.eclipse.hawkbit.auth.SpRole;
|
import org.eclipse.hawkbit.auth.SpRole;
|
||||||
|
import org.eclipse.hawkbit.context.Principal;
|
||||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareUser;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -92,8 +91,8 @@ public class SecurityContextSwitch {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WithUser withController(final String principal, final String... authorities) {
|
public static WithUser withController(final String principal) { // authorized controller
|
||||||
return withTenantAndUser(DEFAULT_TENANT, principal, authorities, true, true);
|
return withTenantAndUser(DEFAULT_TENANT, principal, new String[] { CONTROLLER_ROLE }, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WithUser withUser(final String principal, final String... authorities) {
|
public static WithUser withUser(final String principal, final String... authorities) {
|
||||||
@@ -146,12 +145,8 @@ public class SecurityContextSwitch {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authentication getAuthentication() {
|
public Authentication getAuthentication() {
|
||||||
final TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken(
|
return new TestingAuthenticationToken(
|
||||||
new TenantAwareUser(annotation.principal(), "***", null, annotation.tenant()),
|
new Principal(annotation.tenant(), annotation.principal()), annotation.credentials(), annotation.authorities());
|
||||||
annotation.credentials(), annotation.authorities());
|
|
||||||
testingAuthenticationToken.setDetails(
|
|
||||||
new TenantAwareAuthenticationDetails(annotation.tenant(), annotation.controller()));
|
|
||||||
return testingAuthenticationToken;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ import org.springframework.security.test.context.support.WithSecurityContextFact
|
|||||||
@Inherited
|
@Inherited
|
||||||
public @interface WithUser {
|
public @interface WithUser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the test actor.
|
||||||
|
*
|
||||||
|
* @return test actor
|
||||||
|
*/
|
||||||
|
String principal() default "TestActor";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the test tenant id.
|
* Gets the test tenant id.
|
||||||
*
|
*
|
||||||
@@ -35,13 +42,6 @@ public @interface WithUser {
|
|||||||
*/
|
*/
|
||||||
String tenant() default "DEFAULT";
|
String tenant() default "DEFAULT";
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the test principal.
|
|
||||||
*
|
|
||||||
* @return test principal
|
|
||||||
*/
|
|
||||||
String principal() default "TestPrincipal";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the test credentials.
|
* Gets the test credentials.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
# Test utility properties for easier fault investigation - START
|
# Test utility properties for easier fault investigation - START
|
||||||
## Logging - START
|
## Logging - START
|
||||||
logging.level.root=ERROR
|
logging.level.root=WARN
|
||||||
logging.level.org.eclipse.hawkbit.repository.test.matcher.EventVerifier=ERROR
|
logging.level.org.eclipse.hawkbit.repository.test.matcher.EventVerifier=ERROR
|
||||||
logging.level.org.eclipse.persistence=ERROR
|
logging.level.org.eclipse.persistence=ERROR
|
||||||
spring.jpa.properties.eclipselink.logging.level=FINE
|
spring.jpa.properties.eclipselink.logging.level=FINE
|
||||||
|
|||||||
Reference in New Issue
Block a user