Fix Json serializer defaults (#2663)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-09-10 10:56:19 +03:00
committed by GitHub
parent 6e334d4888
commit 2d45e2a76c
12 changed files with 32 additions and 27 deletions

View File

@@ -31,7 +31,7 @@ public class PropertyBasedArtifactUrlResolverAutoConfiguration {
@ConditionalOnMissingBean(ArtifactUrlResolver.class) @ConditionalOnMissingBean(ArtifactUrlResolver.class)
PropertyBasedArtifactUrlResolver propertyBasedArtifactUrlHandler( PropertyBasedArtifactUrlResolver propertyBasedArtifactUrlHandler(
final PropertyBasedArtifactUrlResolverProperties urlHandlerProperties, final PropertyBasedArtifactUrlResolverProperties urlHandlerProperties,
@Value("${server.servlet.context-path:}") final String contextPath) { @Value("${hawkbit.server.servlet.context-path:}") final String contextPath) {
return new PropertyBasedArtifactUrlResolver(urlHandlerProperties, contextPath); return new PropertyBasedArtifactUrlResolver(urlHandlerProperties, contextPath);
} }
} }

View File

@@ -25,4 +25,4 @@ spring.servlet.multipart.max-file-size=5MB
hawkbit.server.security.dos.maxStatusEntriesPerAction=100 hawkbit.server.security.dos.maxStatusEntriesPerAction=100
hawkbit.server.security.dos.maxAttributeEntriesPerTarget=10 hawkbit.server.security.dos.maxAttributeEntriesPerTarget=10
# Quota - END # Quota - END
org.eclipse.hawkbit.events.remote-enabled=false hawkbit.events.remote-enabled=false

View File

@@ -74,7 +74,7 @@ import org.springframework.test.context.TestPropertySource;
@ActiveProfiles({ "test" }) @ActiveProfiles({ "test" })
@SpringBootTest(classes = { JpaRepositoryConfiguration.class }, webEnvironment = SpringBootTest.WebEnvironment.NONE) @SpringBootTest(classes = { JpaRepositoryConfiguration.class }, webEnvironment = SpringBootTest.WebEnvironment.NONE)
@TestPropertySource(properties = { @TestPropertySource(properties = {
"org.eclipse.hawkbit.events.remote-enabled=false", "hawkbit.events.remote-enabled=false",
"spring.main.allow-bean-definition-overriding=true" }) "spring.main.allow-bean-definition-overriding=true" })
class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest { class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {

View File

@@ -38,7 +38,7 @@ import org.springframework.test.context.TestPropertySource;
// Dirty context is necessary to create a new vhost and recreate all necessary beans after every test class. // Dirty context is necessary to create a new vhost and recreate all necessary beans after every test class.
@DirtiesContext(classMode = ClassMode.AFTER_CLASS) @DirtiesContext(classMode = ClassMode.AFTER_CLASS)
@TestPropertySource(properties = { @TestPropertySource(properties = {
"org.eclipse.hawkbit.events.remote-enabled=false", "hawkbit.events.remote-enabled=false",
"spring.main.allow-bean-definition-overriding=true" }) "spring.main.allow-bean-definition-overriding=true" })
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes @SuppressWarnings("java:S6813") // constructor injects are not possible for test classes
public abstract class AbstractAmqpIntegrationTest extends AbstractIntegrationTest { public abstract class AbstractAmqpIntegrationTest extends AbstractIntegrationTest {

View File

@@ -11,4 +11,4 @@
# Logging START - activate to see request/response details # Logging START - activate to see request/response details
#logging.level.org.eclipse.hawkbit.rest.util.MockMvcResultPrinter=DEBUG #logging.level.org.eclipse.hawkbit.rest.util.MockMvcResultPrinter=DEBUG
# Logging END # Logging END
org.eclipse.hawkbit.events.remote-enabled=false hawkbit.events.remote-enabled=false

View File

@@ -49,8 +49,8 @@ spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.amqp.RabbitA
## Uncomment bellow to Enable communication between services (disabled by default) - no cluster support. ## Uncomment bellow to Enable communication between services (disabled by default) - no cluster support.
# To enable it, enable RabbitMQ (see above) # To enable it, enable RabbitMQ (see above)
# and set below 'org.eclipse.hawkbit.events.remote-enabled=true' # and set below 'hawkbit.events.remote-enabled=true'
org.eclipse.hawkbit.events.remote-enabled=false hawkbit.events.remote-enabled=false
## Disable DMF (by default) - no DMF support. To enable it, enable RabbitMQ (see above) and comment the line ## Disable DMF (by default) - no DMF support. To enable it, enable RabbitMQ (see above) and comment the line
## (hawkbit.dmf.rabbitmq.enabled=false) set hawkbit.dmf.rabbitmq.enabled=true ## (hawkbit.dmf.rabbitmq.enabled=false) set hawkbit.dmf.rabbitmq.enabled=true

View File

@@ -57,9 +57,9 @@ public class RepositoryConfiguration {
@SuppressWarnings("java:S3358") // java:S3358 better readable this way @SuppressWarnings("java:S3358") // java:S3358 better readable this way
RoleHierarchy roleHierarchy( RoleHierarchy roleHierarchy(
// if configured replaces the hierarchy completely // if configured replaces the hierarchy completely
@Value("${org.eclipse.hawkbit.hierarchy:}") final String hierarchy, @Value("${hawkbit.hierarchy:}") final String hierarchy,
// if the "hierarchy" property is empty, and this property is configured it is appended to the default hierarchy // if the "hierarchy" property is empty, and this property is configured it is appended to the default hierarchy
@Value("${org.eclipse.hawkbit.hierarchy.ext:}") final String hierarchyExt) { @Value("${hawkbit.hierarchy.ext:}") final String hierarchyExt) {
return RoleHierarchyImpl.fromHierarchy( return RoleHierarchyImpl.fromHierarchy(
ObjectUtils.isEmpty(hierarchy) ObjectUtils.isEmpty(hierarchy)
? (ObjectUtils.isEmpty(hierarchyExt) ? Hierarchy.DEFAULT : Hierarchy.DEFAULT + hierarchyExt) ? (ObjectUtils.isEmpty(hierarchyExt) ? Hierarchy.DEFAULT : Hierarchy.DEFAULT + hierarchyExt)

View File

@@ -44,13 +44,13 @@ import org.springframework.context.ApplicationEventPublisher;
@Slf4j @Slf4j
public final class EventPublisherHolder { public final class EventPublisherHolder {
@Value("${org.eclipse.hawkbit.events.remote-enabled:true}") @Value("${hawkbit.events.remote-enabled:true}")
private boolean remoteEventsEnabled; private boolean remoteEventsEnabled;
@Value("${org.eclipse.hawkbit.events.remote.destination:fanoutEventChannel}") @Value("${hawkbit.events.remote.destination:fanoutEventChannel}")
private String fanoutEventChannel; private String fanoutEventChannel;
@Value("${org.eclipse.hawkbit.events.remote-service-enabled:true}") @Value("${hawkbit.events.remote-service-enabled:true}")
private boolean remoteServiceEventsEnabled; private boolean remoteServiceEventsEnabled;
@Value("${org.eclipse.hawkbit.events.remote.service.destination:serviceEventChannel}") @Value("${hawkbit.events.remote.service.destination:serviceEventChannel}")
private String serviceEventChannel; private String serviceEventChannel;
@@ -65,7 +65,7 @@ public final class EventPublisherHolder {
@PostConstruct @PostConstruct
private void validateRemoteEventConfig() { private void validateRemoteEventConfig() {
if (remoteEventsEnabled && streamBridge == null) { if (remoteEventsEnabled && streamBridge == null) {
throw new IllegalStateException("'org.eclipse.hawkbit.events.remote-enabled' is true but streamBridge is not configured. Check if 'spring-cloud-starter-stream-rabbit' dependency is included."); throw new IllegalStateException("'hawkbit.events.remote-enabled' is true but streamBridge is not configured. Check if 'spring-cloud-starter-stream-rabbit' dependency is included.");
} }
} }

View File

@@ -120,7 +120,7 @@ public class JpaRolloutManagement implements RolloutManagement {
RolloutStatus.CREATING, RolloutStatus.READY, RolloutStatus.WAITING_FOR_APPROVAL, RolloutStatus.STARTING, RolloutStatus.RUNNING, RolloutStatus.CREATING, RolloutStatus.READY, RolloutStatus.WAITING_FOR_APPROVAL, RolloutStatus.STARTING, RolloutStatus.RUNNING,
RolloutStatus.PAUSED, RolloutStatus.APPROVAL_DENIED); RolloutStatus.PAUSED, RolloutStatus.APPROVAL_DENIED);
@Value("${org.eclipse.hawkbit.repository.jpa.management.rollout.max.actions.per.transaction:5000}") @Value("${hawkbit.repository.jpa.management.rollout.max.actions.per.transaction:5000}")
private int MAX_ACTIONS; private int MAX_ACTIONS;
private final EntityManager entityManager; private final EntityManager entityManager;

View File

@@ -64,4 +64,4 @@ hawkbit.repository.cluster.lock.refreshOnRemainPercent=10
## reduce scheduler tic period to speed up tests ## reduce scheduler tic period to speed up tests
hawkbit.repository.cluster.lock.ticPeriodMS=10 hawkbit.repository.cluster.lock.ticPeriodMS=10
org.eclipse.hawkbit.events.remote-enabled=false hawkbit.events.remote-enabled=false

View File

@@ -80,4 +80,4 @@ hawkbit.server.security.dos.maxActionsPerTarget=20
# Quota - END # Quota - END
# Properties that are managed by autoconfigure module at runtime and not available during test - END # Properties that are managed by autoconfigure module at runtime and not available during test - END
org.eclipse.hawkbit.events.remote-enabled=false hawkbit.events.remote-enabled=false

View File

@@ -32,6 +32,7 @@ import org.eclipse.hawkbit.security.SpringSecurityAuditorAware.AuditorAwarePrinc
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.tenancy.TenantAwareUser; import org.eclipse.hawkbit.tenancy.TenantAwareUser;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
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;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
@@ -106,7 +107,7 @@ public interface SecurityContextSerializer {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final boolean FALLBACK_TO_JAVA_SERIALIZATION = private static final boolean FALLBACK_TO_JAVA_SERIALIZATION =
!Boolean.getBoolean("org.hawkbit.security.contextSerializer.json.no-fallback-to-java"); !Boolean.getBoolean("hawkbit.security.contextSerializer.json.no-fallback-to-java");
@Override @Override
public String serialize(final SecurityContext securityContext) { public String serialize(final SecurityContext securityContext) {
@@ -137,6 +138,8 @@ public interface SecurityContextSerializer {
} }
// 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
// only authenticated user is supported
@NoArgsConstructor @NoArgsConstructor
@Data @Data
private static class SecCtxInfo implements Serializable { private static class SecCtxInfo implements Serializable {
@@ -145,19 +148,21 @@ public interface SecurityContextSerializer {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private String tenant; private String tenant;
private boolean controller;
// auditor / username (authentication principal name) // auditor / username (authentication principal name)
private String auditor; private String auditor = "n/a"; // default value "n/a" is used only on deserialization if field is missing
@JsonProperty(required = true) @JsonProperty(required = true)
private String[] authorities; private String[] authorities;
@JsonProperty(defaultValue = "true")
private boolean authenticated;
SecCtxInfo(final SecurityContext securityContext) { SecCtxInfo(final SecurityContext securityContext) {
final Authentication authentication = securityContext.getAuthentication(); final Authentication authentication = securityContext.getAuthentication();
if (!authentication.isAuthenticated()) {
throw new IllegalStateException("Only authenticated context could be serialized");
}
if (authentication.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareDetails) { if (authentication.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareDetails) {
if (tenantAwareDetails.controller()) {
throw new IllegalStateException("Controller authentication context is not supported");
}
tenant = tenantAwareDetails.tenant(); tenant = tenantAwareDetails.tenant();
controller = tenantAwareDetails.controller();
} else if (authentication.getPrincipal() instanceof TenantAwareUser tenantAwareUser) { } else if (authentication.getPrincipal() instanceof TenantAwareUser tenantAwareUser) {
tenant = tenantAwareUser.getTenant(); tenant = tenantAwareUser.getTenant();
} }
@@ -167,10 +172,10 @@ public interface SecurityContextSerializer {
// 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 = SpringSecurityAuditorAware.resolveAuditor(authentication); auditor = SpringSecurityAuditorAware.resolveAuditor(authentication);
authorities = authentication.getAuthorities().stream().map(Object::toString).toArray(String[]::new); authorities = authentication.getAuthorities().stream().map(Object::toString).toArray(String[]::new);
authenticated = authentication.isAuthenticated();
} }
// allows setting for auditor also as username (so supported auditor/username in json) // TODO - remove it in future
// auditor alias, allows setting for auditor also as username (so supported auditor/username in json)
@JsonSetter("username") @JsonSetter("username")
private void setUsername(final String username) { private void setUsername(final String username) {
this.auditor = username; this.auditor = username;
@@ -178,7 +183,7 @@ public interface SecurityContextSerializer {
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, controller); final Object details = tenant == null ? null : new TenantAwareAuthenticationDetails(tenant, false);
final AuditorAwarePrincipal principal = () -> auditor; final AuditorAwarePrincipal principal = () -> auditor;
final Collection<? extends GrantedAuthority> grantedAuthorities = final Collection<? extends GrantedAuthority> grantedAuthorities =
Stream.of(authorities).map(SimpleGrantedAuthority::new).toList(); Stream.of(authorities).map(SimpleGrantedAuthority::new).toList();
@@ -196,7 +201,7 @@ public interface SecurityContextSerializer {
@Override @Override
public boolean isAuthenticated() { public boolean isAuthenticated() {
return authenticated; return true;
} }
@Override @Override