Skip to content

Commit

Permalink
Feature/ctx aware and access controller2 (#1456)
Browse files Browse the repository at this point in the history
* Introduce the AccessControlManager and use if for the TargetManagement and TargetTypeManagement.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Extend the access control manager by an API to serialize the current active context and persist it for scheduled background operations like auto-assignment.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Verify modification is permitted before performing automatic assignment

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Start with controlling distribution set type access. Perform some refactoring.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Support distribution set access control. Increase character limit to 512 chars for access control context. Refactor default implementations.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce ContextRunner and define admin execution to check for duplicates before creating/updating entities.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce Software Module, Module Type and Artifact control management. Fix tests.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce access controlling test base. Add first test verifying the read operations for target types.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Finalize target type access controlling test.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce ContextRunnerTest and TargetAccessControllingTest.
Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce DistributionSetAccessControllingTest and fix missing access control specifications.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Extend test cases. Include only updatable targets into rollout.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Fix action visibility.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Modifiable->Updatable & UPDATE check where needed

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* ContextRunner superseded by ContextAware

+ ContextRunner remaned to ContextAware (move as a cenral entry/concept).
  It now extends (and replace) TenantAware
+ SecurityContextTenantAware becomes ContextAware
+ Pluggable serialization mechanism
  (default Java serialization of contexts) for SecurityContextTenantAware
  (using SecurityContextSerializer)
+ AccessControl methods are added to ensure no entities fill be retrieved
  just to call access control - so, if all permitted - no additional db
  queries will be made
+ &lt;repo type&gt;AccessControl classes removed and replaced with
  AccessControl &lt;repo type&gt; generics
+ AccessControlService removed - every AccessControl is registered and
  overiden independently
+ access_control_context in DB increased to 4k (in order to support java
  security context serialization)
+ needed adaptaion of implemtation and tests done

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Refactor SoftModules & DistSets

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Refactoring of the Repositories

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Repostiotory level permissions

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Improvements

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Simplification of AccessControl interface

* Simplifications & management package

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Implementation improvements

+ Artifact management & repo reviewed and tuned
+ Action(Status) management & repo reviewed and tuned
+ SoftwareModule(Type/Meta) management & repo reviewed and tuned
+ DistributionSet(Type/Tag/Meta) management(+Invalidation) & repo reviewed and tuned
+ Target(Tag/Type/Meta) management & repo reviewed and tuned
+ TargetQueryFilter management & repo reviewed and tuned

* Apply suggestions from code review

Suggestions accepted. Thanks @herdt-michael

Co-authored-by: Michael Herdt <michael.herdt@bosch.com>

* Apply suggestions from code review 2

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

---------

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>
Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
Co-authored-by: Michael Herdt <Michael.Herdt@bosch.com>
  • Loading branch information
avgustinmm and herdt-michael authored Nov 16, 2023
1 parent 8d487fd commit b982039
Show file tree
Hide file tree
Showing 170 changed files with 5,374 additions and 3,230 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@
import java.util.Map;
import java.util.stream.Collectors;

import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.autoconfigure.security.MultiUserProperties.User;
import org.eclipse.hawkbit.im.authentication.PermissionService;
import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.InMemoryUserAuthoritiesResolver;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.SecurityContextSerializer;
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SpringSecurityAuditorAware;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
Expand All @@ -49,20 +52,22 @@
public class SecurityAutoConfiguration {

/**
* Creates a {@link TenantAware} bean based on the given
* {@link UserAuthoritiesResolver}.
*
* Creates a {@link ContextAware} (hence {@link TenantAware}) bean based on the given
* {@link UserAuthoritiesResolver} and {@link SecurityContextSerializer}.
*
* @param authoritiesResolver
* The user authorities/roles resolver
*
* @return the {@link TenantAware} singleton bean which holds the current
* {@link TenantAware} service and make it accessible in beans which
* cannot access the service directly, e.g. JPA entities.
* @param securityContextSerializer
* The security context serializer.
*
* @return the {@link ContextAware} singleton bean.
*/
@Bean
@ConditionalOnMissingBean
public TenantAware tenantAware(final UserAuthoritiesResolver authoritiesResolver) {
return new SecurityContextTenantAware(authoritiesResolver);
public ContextAware contextAware(
final UserAuthoritiesResolver authoritiesResolver,
@Autowired(required = false) final SecurityContextSerializer securityContextSerializer) {
return new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer);
}

/**
Expand Down
62 changes: 62 additions & 0 deletions hawkbit-core/src/main/java/org/eclipse/hawkbit/ContextAware.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Copyright (c) 2023 Bosch.IO 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;

import org.eclipse.hawkbit.tenancy.TenantAware;

import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

/**
* {@link ContextAware} provides means for getting the current context (via {@link #getCurrentContext()}) and then
* to execute a {@link Runnable} or a {@link Function} in the same context using {@link #runInContext(String, Runnable)}
* or {@link #runInContext(String, Function, Object)}.
* <p/>
* This is useful for scheduled background operations like rollouts and auto assignments where they shall
* be processed in the scope of the creator.
*/
public interface ContextAware extends TenantAware {

/**
* Return the current context encoded as a {@link String}. Depending on the implementation it could,
* for instance, be a serialized context or a reference to such.
*
* @return could be empty if there is nothing to serialize or context aware is not supported.
*/
Optional<String> getCurrentContext();

/**
* Wrap a specific execution in a known and pre-serialized context.
*
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
*
* @param serializedContext created by {@link #getCurrentContext()}. Must be non-<code>null</code>.
* @param function function to call in the reconstructed context. Must be non-<code>null</code>.
* @param t the argument that will be passed to the function
* @return the function result
*/
<T, R> R runInContext(String serializedContext, Function<T, R> function, T t);

/**
* Wrap a specific execution in a known and pre-serialized context.
*
* @param serializedContext created by {@link #getCurrentContext()}. Must be non-<code>null</code>.
* @param runnable runnable to call in the reconstructed context. Must be non-<code>null</code>.
*/
default void runInContext(String serializedContext, Runnable runnable) {
Objects.requireNonNull(runnable);
runInContext(serializedContext, v -> {
runnable.run();
return null;
}, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ public interface TenantAware {
* the runner which is implemented to run this specific code
* under the given tenant
* @return the return type of the {@link TenantRunner}
* @throws any
* kind of {@link RuntimeException}
*/
<T> T runAsTenant(String tenant, TenantRunner<T> tenantRunner);

Expand All @@ -60,8 +58,6 @@ public interface TenantAware {
* the runner which is implemented to run this specific code
* under the given tenant
* @return the return type of the {@link TenantRunner}
* @throws any
* kind of {@link RuntimeException}
*/
<T> T runAsTenantAsUser(String tenant, String username, TenantRunner<T> tenantRunner);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ protected void onMultiAction(final MultiActionEvent multiActionEvent) {
private List<Target> getTargetsWithoutPendingCancellations(final Set<String> controllerIds) {
return partitionedParallelExecution(controllerIds, partition -> {
return targetManagement.getByControllerID(partition).stream().filter(target -> {
if (hasPendingCancellations(target.getControllerId())) {
if (hasPendingCancellations(target.getId())) {
LOG.debug("Target {} has pending cancellations. Will not send update message to it.",
target.getControllerId());
return false;
Expand Down Expand Up @@ -469,8 +469,8 @@ private boolean isFromSelf(final RemoteApplicationEvent event) {
return serviceMatcher == null || serviceMatcher.isFromSelf(event);
}

private boolean hasPendingCancellations(final String controllerId) {
return deploymentManagement.hasPendingCancellations(controllerId);
private boolean hasPendingCancellations(final Long targetId) {
return deploymentManagement.hasPendingCancellations(targetId);
}

protected void sendCancelMessageToTarget(final String tenant, final String controllerId, final Long actionId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.eclipse.hawkbit.security.DdiSecurityProperties.Rp;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource;
import org.eclipse.hawkbit.security.SecurityContextSerializer;
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
Expand Down Expand Up @@ -113,6 +114,9 @@ public class AmqpControllerAuthenticationTest {
@Mock
private UserAuthoritiesResolver authoritiesResolver;

@Mock
private SecurityContextSerializer securityContextSerializer;

@Mock
private RabbitTemplate rabbitTemplate;

Expand Down Expand Up @@ -147,7 +151,7 @@ public void before() {
when(tenantConfigurationManagementMock.getConfigurationValue(any(), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_FALSE);

final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver);
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer);
final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware);

authenticationManager = new AmqpControllerAuthentication(systemManagement, controllerManagement,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,10 @@ void testSendDownloadRequestWithSoftwareModulesAndNoArtifacts() {
assertThat(softwareModule.getArtifacts().isEmpty()).as("Artifact list for softwaremodule should be empty")
.isTrue();

assertThat(softwareModule.getMetadata()).containsExactly(
new DmfMetadata(TestdataFactory.VISIBLE_SM_MD_KEY, TestdataFactory.VISIBLE_SM_MD_VALUE));

assertThat(softwareModule.getMetadata()).allSatisfy(metadata -> {
assertThat(metadata.getKey()).isEqualTo(TestdataFactory.VISIBLE_SM_MD_KEY);
assertThat(metadata.getValue()).isEqualTo(TestdataFactory.VISIBLE_SM_MD_VALUE);
});
for (final SoftwareModule softwareModule2 : action.getDistributionSet().getModules()) {
if (!softwareModule.getModuleId().equals(softwareModule2.getId())) {
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource;
import org.eclipse.hawkbit.security.SecurityContextSerializer;
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext;
Expand Down Expand Up @@ -148,6 +149,9 @@ public class AmqpMessageHandlerServiceTest {
@Mock
private UserAuthoritiesResolver authoritiesResolver;

@Mock
private SecurityContextSerializer securityContextSerializer;

@Captor
private ArgumentCaptor<Map<String, String>> attributesCaptor;

Expand Down Expand Up @@ -179,7 +183,7 @@ public void before() throws Exception {
lenient().when(tenantConfigurationManagement.getConfigurationValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class))
.thenReturn(multiAssignmentConfig);

final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver);
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer);
final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware);

amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ void cancelNotAllowActionStatus() {
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 6),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 2) })
void receiveDownLoadAndInstallMessageAfterAssignment() {
void receiveDownloadAndInstallMessageAfterAssignment() {
final String controllerId = TARGET_PREFIX + "receiveDownLoadAndInstallMessageAfterAssignment";

// setup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,6 @@ public interface ArtifactManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
Artifact create(@NotNull @Valid ArtifactUpload artifactUpload);

/**
* Garbage collects artifact binaries if only referenced by given
* {@link SoftwareModule#getId()} or {@link SoftwareModule}'s that are
* marked as deleted.
*
*
* @param artifactSha1Hash
* no longer needed
* @param moduleId
* the garbage collection call is made for
*
* @return <code>true</code> if an binary was actually garbage collected
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
boolean clearArtifactBinary(@NotEmpty String artifactSha1Hash, long moduleId);

/**
* Deletes {@link Artifact} based on given id.
*
Expand Down Expand Up @@ -151,28 +135,28 @@ public interface ArtifactManagement {
*
* @param pageReq
* Pageable parameter
* @param swId
* @param softwareModuleId
* software module id
* @return Page<Artifact>
*
* @throws EntityNotFoundException
* if software module with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Page<Artifact> findBySoftwareModule(@NotNull Pageable pageReq, long swId);
Page<Artifact> findBySoftwareModule(@NotNull Pageable pageReq, long softwareModuleId);

/**
* Count local artifacts for a base software module.
*
* @param swId
* @param softwareModuleId
* software module id
* @return count by software module
*
* @throws EntityNotFoundException
* if software module with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
long countBySoftwareModule(long swId);
long countBySoftwareModule(long softwareModuleId);

/**
* Loads {@link DbArtifact} from store for given {@link Artifact}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,17 +472,6 @@ Target updateControllerAttributes(@NotEmpty String controllerId, @NotNull Map<St
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
void updateActionExternalRef(long actionId, @NotEmpty String externalRef);

/**
* Retrieves list of {@link Action}s which matches the provided
* externalRefs.
*
* @param externalRefs
* for which the actions need to be fetched.
* @return list of {@link Action}s matching the externalRefs.
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
List<Action> getActiveActionsByExternalRef(@NotNull List<String> externalRefs);

/**
* Retrieves an {@link Action} using {@link Action#getExternalRef()}
*
Expand Down
Loading

0 comments on commit b982039

Please sign in to comment.