Feature/ctx aware and access controller2 (#1456)
* 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 + <repo type>AccessControl classes removed and replaced with AccessControl <repo type> 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>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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.security;
|
||||
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
|
||||
public interface SecurityContextSerializer {
|
||||
|
||||
/**
|
||||
* Serializer that do not serialize (returns null on {@link #serialize(SecurityContext)}) and
|
||||
* throws exception on {@link #deserialize(String)}.
|
||||
*/
|
||||
SecurityContextSerializer NOP = new Nop();
|
||||
/**
|
||||
* Serializer the uses Java serialization of {@link java.io.Serializable} objects.
|
||||
* <p/>
|
||||
* Note that serialized via java serialization context might become unreadable if incompatible
|
||||
* changes are made to the object classes.
|
||||
*/
|
||||
SecurityContextSerializer JAVA_SERIALIZATION = new JavaSerialization();
|
||||
|
||||
/**
|
||||
* Return security context as string (could be just a reference)
|
||||
*
|
||||
* @param securityContext the security context
|
||||
* @return the securityContext as string
|
||||
*/
|
||||
String serialize(SecurityContext securityContext);
|
||||
|
||||
/**
|
||||
* Deserialize security context
|
||||
*
|
||||
* @param securityContextString string representing the security context
|
||||
* @return deserialized security context
|
||||
*/
|
||||
SecurityContext deserialize(String securityContextString);
|
||||
|
||||
/**
|
||||
* Empty implementation. Could be used if the serialization shall not be used.
|
||||
* It returns <code>null</code> as serialized context and throws exception if
|
||||
* someone try to deserialize anything.
|
||||
*/
|
||||
class Nop implements SecurityContextSerializer {
|
||||
|
||||
private Nop() {}
|
||||
|
||||
@Override
|
||||
public String serialize(final SecurityContext securityContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecurityContext deserialize(String securityContextString) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation based on the java serialization.
|
||||
*/
|
||||
class JavaSerialization implements SecurityContextSerializer {
|
||||
|
||||
private JavaSerialization() {}
|
||||
|
||||
@Override
|
||||
public String serialize(final SecurityContext securityContext) {
|
||||
Objects.requireNonNull(securityContext);
|
||||
try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
final ObjectOutputStream oos = new ObjectOutputStream(baos)) {
|
||||
oos.writeObject(securityContext);
|
||||
oos.flush();
|
||||
return Base64.getEncoder().encodeToString(baos.toByteArray());
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecurityContext deserialize(String securityContextString) {
|
||||
Objects.requireNonNull(securityContextString);
|
||||
try (final ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(securityContextString));
|
||||
final ObjectInputStream ois = new ObjectInputStream(bais)) {
|
||||
return (SecurityContext) ois.readObject();
|
||||
} catch (final IOException | ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,38 +9,58 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.security;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.ContextAware;
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
|
||||
import org.eclipse.hawkbit.im.authentication.UserPrincipal;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
|
||||
import org.springframework.lang.Nullable;
|
||||
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.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
|
||||
/**
|
||||
* A {@link TenantAware} implementation which retrieves the ID of the tenant
|
||||
* A {@link ContextAware} (hence of {@link TenantAware}) that uses spring security context propagation
|
||||
* mechanisms and which retrieves the ID of the tenant
|
||||
* from the {@link SecurityContext#getAuthentication()}
|
||||
* {@link Authentication#getDetails()} which holds the
|
||||
* {@link TenantAwareAuthenticationDetails} object.
|
||||
*
|
||||
*/
|
||||
public class SecurityContextTenantAware implements TenantAware {
|
||||
public class SecurityContextTenantAware implements ContextAware {
|
||||
|
||||
public static final String SYSTEM_USER = "system";
|
||||
private static final Collection<? extends GrantedAuthority> SYSTEM_AUTHORITIES = Collections
|
||||
.singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.SYSTEM_ROLE));
|
||||
|
||||
private final UserAuthoritiesResolver authoritiesResolver;
|
||||
private final SecurityContextSerializer securityContextSerializer;
|
||||
|
||||
/**
|
||||
* Creates the {@link SecurityContextTenantAware} based on the given
|
||||
* {@link UserAuthoritiesResolver}.
|
||||
*
|
||||
* @param authoritiesResolver
|
||||
* Resolver to retrieve the authorities for a given user. Must
|
||||
* not be <code>null</code>..
|
||||
*/
|
||||
public SecurityContextTenantAware(final UserAuthoritiesResolver authoritiesResolver) {
|
||||
this.authoritiesResolver = authoritiesResolver;
|
||||
this.securityContextSerializer = SecurityContextSerializer.NOP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link SecurityContextTenantAware} based on the given
|
||||
@@ -49,9 +69,12 @@ public class SecurityContextTenantAware implements TenantAware {
|
||||
* @param authoritiesResolver
|
||||
* Resolver to retrieve the authorities for a given user. Must
|
||||
* not be <code>null</code>.
|
||||
* @param securityContextSerializer
|
||||
* Serializer that is used to serialize / deserialize {@link SecurityContext}s.
|
||||
*/
|
||||
public SecurityContextTenantAware(final UserAuthoritiesResolver authoritiesResolver) {
|
||||
public SecurityContextTenantAware(final UserAuthoritiesResolver authoritiesResolver, @Nullable final SecurityContextSerializer securityContextSerializer) {
|
||||
this.authoritiesResolver = authoritiesResolver;
|
||||
this.securityContextSerializer = securityContextSerializer == null ? SecurityContextSerializer.NOP : securityContextSerializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,12 +113,39 @@ public class SecurityContextTenantAware implements TenantAware {
|
||||
|
||||
@Override
|
||||
public <T> T runAsTenantAsUser(final String tenant, final String username, final TenantRunner<T> tenantRunner) {
|
||||
Objects.requireNonNull(tenant);
|
||||
Objects.requireNonNull(username);
|
||||
final List<SimpleGrantedAuthority> authorities = runAsSystem(
|
||||
() -> authoritiesResolver.getUserAuthorities(tenant, username).stream().map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toList()));
|
||||
return runInContext(buildUserSecurityContext(tenant, username, authorities), tenantRunner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getCurrentContext() {
|
||||
return Optional.ofNullable(SecurityContextHolder.getContext()).map(securityContextSerializer::serialize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, R> R runInContext(final String serializedContext, final Function<T, R> function, final T t) {
|
||||
Objects.requireNonNull(serializedContext);
|
||||
Objects.requireNonNull(function);
|
||||
final SecurityContext securityContext = securityContextSerializer.deserialize(serializedContext);
|
||||
Objects.requireNonNull(securityContext);
|
||||
|
||||
final SecurityContext originalContext = SecurityContextHolder.getContext();
|
||||
if (Objects.equals(securityContext, originalContext)) {
|
||||
return function.apply(t);
|
||||
} else {
|
||||
SecurityContextHolder.setContext(securityContext);
|
||||
try {
|
||||
return function.apply(t);
|
||||
} finally {
|
||||
SecurityContextHolder.setContext(originalContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T runInContext(final SecurityContext context, final TenantRunner<T> tenantRunner) {
|
||||
final SecurityContext originalContext = SecurityContextHolder.getContext();
|
||||
try {
|
||||
@@ -122,7 +172,7 @@ public class SecurityContextTenantAware implements TenantAware {
|
||||
|
||||
private static SecurityContext buildUserSecurityContext(final String tenant, final String username,
|
||||
final Collection<? extends GrantedAuthority> authorities) {
|
||||
final SecurityContextImpl securityContext = new SecurityContextImpl();
|
||||
final SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||
securityContext.setAuthentication(new AuthenticationDelegate(
|
||||
SecurityContextHolder.getContext().getAuthentication(), tenant, username, authorities));
|
||||
return securityContext;
|
||||
@@ -134,6 +184,7 @@ public class SecurityContextTenantAware implements TenantAware {
|
||||
* a specific tenant and user.
|
||||
*/
|
||||
private static final class AuthenticationDelegate implements Authentication {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Authentication delegate;
|
||||
@@ -151,12 +202,7 @@ public class SecurityContextTenantAware implements TenantAware {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object another) {
|
||||
if (delegate != null) {
|
||||
return delegate.equals(another);
|
||||
} else if (another == null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return Objects.equals(delegate, another);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user