Refactor hawkbit-core (#1967)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-11-08 13:11:59 +02:00
committed by GitHub
parent ade5723c8c
commit 73253abce0
8 changed files with 72 additions and 108 deletions

View File

@@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -38,11 +39,11 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
private static final String TEMP_FILE_SUFFIX = "artifactrepo";
@Override
// suppress warning, of not strong enough hashing algorithm, SHA-1 and MD5
// is not used security related
// suppress warning, of not strong enough hashing algorithm, SHA-1 and MD5 is not used security related
@SuppressWarnings("squid:S2070")
public AbstractDbArtifact store(final String tenant, final InputStream content, final String filename,
final String contentType, final DbArtifactHash providedHashes) {
public AbstractDbArtifact store(final String tenant,
final InputStream content, final String filename, final String contentType,
final DbArtifactHash providedHashes) {
final MessageDigest mdSHA1;
final MessageDigest mdMD5;
final MessageDigest mdSHA256;
@@ -56,7 +57,6 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
String tempFile = null;
try (final DigestInputStream inputStream = wrapInDigestInputStream(content, mdSHA1, mdMD5, mdSHA256)) {
tempFile = storeTempFile(inputStream);
final HexFormat hexFormat = HexFormat.of().withLowerCase();
@@ -65,19 +65,18 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
final String md5Hash16 = hexFormat.formatHex(mdMD5.digest());
final String sha256Hash16 = hexFormat.formatHex(mdSHA256.digest());
checkHashes(sha1Hash16, md5Hash16, sha256Hash16, providedHashes);
checkHashes(providedHashes, sha1Hash16, md5Hash16, sha256Hash16);
// Check if file with same sha1 hash exists and if so return it
if (existsByTenantAndSha1(tenant, sha1Hash16)) {
return addMissingHashes(getArtifactBySha1(tenant, sha1Hash16), sha1Hash16, md5Hash16, sha256Hash16);
}
return store(sanitizeTenant(tenant), new DbArtifactHash(sha1Hash16, md5Hash16, sha256Hash16), contentType,
tempFile);
return store(sanitizeTenant(tenant), new DbArtifactHash(sha1Hash16, md5Hash16, sha256Hash16), contentType, tempFile);
} catch (final IOException e) {
throw new ArtifactStoreException(e.getMessage(), e);
} finally {
if (!StringUtils.isEmpty(tempFile)) {
if (!ObjectUtils.isEmpty(tempFile)) {
deleteTempFile(tempFile);
}
}
@@ -91,7 +90,7 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
final File file = new File(tempFile);
try {
Files.deleteIfExists(file.toPath());
} catch (IOException e) {
} catch (final IOException e) {
log.error("Could not delete temp file {} ({})", file, e.getMessage());
}
}
@@ -110,30 +109,31 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
private static File createTempFile() {
try {
return Files.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX).toFile();
final File file = Files.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX).toFile();
file.deleteOnExit();
return file;
} catch (final IOException e) {
throw new ArtifactStoreException("Cannot create tempfile", e);
throw new ArtifactStoreException("Cannot create temp file", e);
}
}
private static void checkHashes(final String sha1Hash16, final String md5Hash16, final String sha256Hash16,
final DbArtifactHash providedHashes) {
private static void checkHashes(final DbArtifactHash providedHashes,
final String sha1Hash16, final String md5Hash16, final String sha256Hash16) {
if (providedHashes == null) {
return;
}
if (areHashesNotMatching(providedHashes.getSha1(), sha1Hash16)) {
throw new HashNotMatchException("The given sha1 hash " + providedHashes.getSha1()
+ " does not match the calculated sha1 hash " + sha1Hash16, HashNotMatchException.SHA1);
throw new HashNotMatchException("The given sha1 hash " + providedHashes.getSha1() +
" does not match the calculated sha1 hash " + sha1Hash16, HashNotMatchException.SHA1);
}
if (areHashesNotMatching(providedHashes.getMd5(), md5Hash16)) {
throw new HashNotMatchException("The given md5 hash " + providedHashes.getMd5()
+ " does not match the calculated md5 hash " + md5Hash16, HashNotMatchException.MD5);
throw new HashNotMatchException("The given md5 hash " + providedHashes.getMd5() +
" does not match the calculated md5 hash " + md5Hash16, HashNotMatchException.MD5);
}
if (areHashesNotMatching(providedHashes.getSha256(), sha256Hash16)) {
throw new HashNotMatchException(
"The given sha256 hash " + providedHashes.getSha256()
+ " does not match the calculated sha256 hash " + sha256Hash16,
HashNotMatchException.SHA256);
throw new HashNotMatchException("The given sha256 hash " + providedHashes.getSha256() +
" does not match the calculated sha256 hash " + sha256Hash16, HashNotMatchException.SHA256);
}
}
@@ -141,14 +141,13 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
return providedHashValue != null && !hashValue.equals(providedHashValue);
}
private static DigestInputStream wrapInDigestInputStream(final InputStream input, final MessageDigest mdSHA1,
final MessageDigest mdMD5, final MessageDigest mdSHA256) {
private static DigestInputStream wrapInDigestInputStream(final InputStream input,
final MessageDigest mdSHA1, final MessageDigest mdMD5, final MessageDigest mdSHA256) {
return new DigestInputStream(new DigestInputStream(new DigestInputStream(input, mdSHA256), mdMD5), mdSHA1);
}
private AbstractDbArtifact addMissingHashes(final AbstractDbArtifact existing, final String calculatedSha1,
final String calculatedMd5, final String calculatedSha256) {
private AbstractDbArtifact addMissingHashes(final AbstractDbArtifact existing,
final String calculatedSha1, final String calculatedMd5, final String calculatedSha256) {
final String sha1 = checkEmpty(existing.getHashes().getSha1(), calculatedSha1);
final String md5 = checkEmpty(existing.getHashes().getMd5(), calculatedMd5);
final String sha256 = checkEmpty(existing.getHashes().getSha256(), calculatedSha256);
@@ -158,6 +157,6 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
}
private String checkEmpty(final String value, final String fallback) {
return StringUtils.isEmpty(value) ? fallback : value;
return ObjectUtils.isEmpty(value) ? fallback : value;
}
}

View File

@@ -29,15 +29,14 @@ public interface ArtifactRepository {
* @param content the content to store
* @param filename the filename of the artifact
* @param contentType the content type of the artifact
* @param hash the hashes of the artifact to do hash-checks after storing the
* artifact, might be {@code null}
* @param hash the hashes of the artifact to do hash-checks after storing the artifact, might be {@code null}
* @return the stored artifact
* @throws MethodNotSupportedException if implementation does not support the operation
* @throws UnsupportedOperationException if implementation does not support the operation
* @throws ArtifactStoreException in case storing of the artifact was not successful
* @throws HashNotMatchException in case {@code hash} is provided and not matching to the
* calculated hashes during storing
* @throws HashNotMatchException in case {@code hash} is provided and not matching to the calculated hashes during storing
*/
AbstractDbArtifact store(@NotEmpty String tenant, @NotNull InputStream content, @NotEmpty String filename,
AbstractDbArtifact store(
@NotEmpty String tenant, @NotNull InputStream content, @NotEmpty String filename,
String contentType, DbArtifactHash hash);
/**
@@ -45,7 +44,7 @@ public interface ArtifactRepository {
*
* @param tenant the tenant to store the artifact
* @param sha1Hash the sha1-hash of the artifact to delete
* @throws MethodNotSupportedException if implementation does not support the operation
* @throws UnsupportedOperationException if implementation does not support the operation
*/
void deleteBySha1(@NotEmpty String tenant, @NotEmpty String sha1Hash);
@@ -55,7 +54,7 @@ public interface ArtifactRepository {
* @param tenant the tenant to store the artifact
* @param sha1Hash the sha1-hash of the file to lookup.
* @return The artifact file object or {@code null} if no file exists.
* @throws MethodNotSupportedException if implementation does not support the operation
* @throws UnsupportedOperationException if implementation does not support the operation
*/
AbstractDbArtifact getArtifactBySha1(@NotEmpty String tenant, @NotEmpty String sha1Hash);
@@ -74,4 +73,4 @@ public interface ArtifactRepository {
* @return the boolean whether the artifact exists or not
*/
boolean existsByTenantAndSha1(@NotEmpty String tenant, @NotEmpty String sha1Hash);
}
}

View File

@@ -9,12 +9,14 @@
*/
package org.eclipse.hawkbit.artifact.repository;
import java.io.Serial;
/**
* {@link ArtifactStoreException} is thrown in case storing of an artifact was
* not successful.
* {@link ArtifactStoreException} is thrown in case storing of an artifact was not successful.
*/
public class ArtifactStoreException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
/**
@@ -26,22 +28,4 @@ public class ArtifactStoreException extends RuntimeException {
public ArtifactStoreException(final String message, final Throwable cause) {
super(message, cause);
}
/**
* Constructs a ArtifactStoreException with message.
*
* @param message the message of the exception
*/
public ArtifactStoreException(final String message) {
super(message);
}
/**
* Constructs a ArtifactStoreException with cause.
*
* @param cause of the exception
*/
public ArtifactStoreException(final Throwable cause) {
super(cause);
}
}

View File

@@ -9,29 +9,24 @@
*/
package org.eclipse.hawkbit.artifact.repository;
import java.io.Serial;
import lombok.Getter;
/**
* Thrown when provided hashes and hashes caluclated during storing are not
* matching.
* Thrown when provided hashes and hashes calculated during storing are not matching.
*/
public class HashNotMatchException extends RuntimeException {
public static final String SHA1 = "SHA-1";
public static final String MD5 = "MD5";
public static final String SHA256 = "SHA-256";
private static final long serialVersionUID = 1L;
private final String hashFunction;
/**
* Constructs a HashNotMatchException with message and cause.
*
* @param message the message of the exception
* @param cause the cause of the exception
* @param hashFunction the hash function which caused this exception
*/
public HashNotMatchException(final String message, final Throwable cause, final String hashFunction) {
super(message, cause);
this.hashFunction = hashFunction;
}
@Serial
private static final long serialVersionUID = 1L;
@Getter
private final String hashFunction;
/**
* Constructs a HashNotMatchException with message.
@@ -43,11 +38,4 @@ public class HashNotMatchException extends RuntimeException {
super(message);
this.hashFunction = hashFunction;
}
/**
* @return the hashFunction
*/
public String getHashFunction() {
return hashFunction;
}
}

View File

@@ -18,7 +18,7 @@ import org.springframework.cache.CacheManager;
public interface TenancyCacheManager extends CacheManager {
/**
* A direct access for retrieving the cache without including the current
* A direct-access for retrieving the cache without including the current
* tenant key. This is necessary e.g. for retrieving caches not for the
* current tenant.
*
@@ -33,5 +33,5 @@ public interface TenancyCacheManager extends CacheManager {
*
* @param tenant the tenant to evict caches
*/
void evictCaches(final String tenant);
}
void evictCaches(String tenant);
}

View File

@@ -32,7 +32,6 @@ public class TenantAwareCacheManager implements TenancyCacheManager {
private static final String TENANT_CACHE_DELIMITER = "|";
private final CacheManager delegate;
private final TenantAware tenantAware;
/**
@@ -71,7 +70,7 @@ public class TenantAwareCacheManager implements TenancyCacheManager {
}
/**
* A direct access for retrieving all cache names overall tenants.
* A direct-access for retrieving all cache names overall tenants.
*
* @return all cache names without tenant check
*/
@@ -86,7 +85,7 @@ public class TenantAwareCacheManager implements TenancyCacheManager {
@Override
public void evictCaches(final String tenant) {
getCacheNames(tenant).forEach(cachename -> delegate.getCache(buildKey(tenant, cachename)).clear());
getCacheNames(tenant).forEach(cacheName -> delegate.getCache(buildKey(tenant, cacheName)).clear());
}
private static boolean isTenantInvalid(final String tenant) {
@@ -99,7 +98,9 @@ public class TenantAwareCacheManager implements TenancyCacheManager {
private Collection<String> getCacheNames(final String tenant) {
final String tenantWithDelimiter = tenant + TENANT_CACHE_DELIMITER;
return delegate.getCacheNames().parallelStream().filter(cacheName -> cacheName.startsWith(tenantWithDelimiter))
.map(cacheName -> cacheName.substring(tenantWithDelimiter.length())).collect(Collectors.toList());
return delegate.getCacheNames().parallelStream()
.filter(cacheName -> cacheName.startsWith(tenantWithDelimiter))
.map(cacheName -> cacheName.substring(tenantWithDelimiter.length()))
.toList();
}
}
}

View File

@@ -42,23 +42,19 @@ public interface TenantAware {
<T> T runAsTenant(String tenant, TenantRunner<T> tenantRunner);
/**
* Gives the possibility to run a certain code under a specific given
* {@code tenant} and {@code username}. Only the given {@link TenantRunner} is executed under the
* specific tenant and user e.g. under control of an {@link ThreadLocal}. After the
* {@link TenantRunner} it must be ensured that the original tenant before
* this invocation is reset.
* Gives the possibility to run a certain code under a specific given {@code tenant} and {@code username}.
* Only the given {@link TenantRunner} is executed under the specific tenant and user e.g. under control of an {@link ThreadLocal}.
* After the {@link TenantRunner} it must be ensured that the original tenant before this invocation is reset.
*
* @param tenant the tenant which the specific code should run with
* @param username the username which the specific code should run with
* @param tenantRunner the runner which is implemented to run this specific code
* under the given tenant
* @param tenantRunner the runner which is implemented to run this specific code under the given tenant
* @return the return type of the {@link TenantRunner}
*/
<T> T runAsTenantAsUser(String tenant, String username, TenantRunner<T> tenantRunner);
/**
* An {@link TenantRunner} interface which allows to run specific code under
* a given tenant by using the
* An {@link TenantRunner} interface which allows to run specific code under a given tenant by using the
* {@link TenantAware#runAsTenant(String, TenantRunner)}.
*
* @param <T> the return type of the runner

View File

@@ -17,7 +17,6 @@ import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
@@ -36,14 +35,13 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUser;
/**
* 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.
* 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 ContextAware {
public static final String SYSTEM_USER = "system";
private static final Collection<? extends GrantedAuthority> SYSTEM_AUTHORITIES =
Collections.singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.SYSTEM_ROLE));
@@ -51,8 +49,7 @@ public class SecurityContextTenantAware implements ContextAware {
private final SecurityContextSerializer securityContextSerializer;
/**
* Creates the {@link SecurityContextTenantAware} based on the given
* {@link UserAuthoritiesResolver}.
* 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>..
@@ -63,8 +60,7 @@ public class SecurityContextTenantAware implements ContextAware {
}
/**
* Creates the {@link SecurityContextTenantAware} based on the given
* {@link UserAuthoritiesResolver}.
* 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>.
* @param securityContextSerializer Serializer that is used to serialize / deserialize {@link SecurityContext}s.
@@ -115,8 +111,9 @@ public class SecurityContextTenantAware implements ContextAware {
Objects.requireNonNull(username);
final List<SimpleGrantedAuthority> authorities = runAsSystem(
() -> authoritiesResolver.getUserAuthorities(tenant, username).stream().map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()));
() -> authoritiesResolver.getUserAuthorities(tenant, username).stream()
.map(SimpleGrantedAuthority::new)
.toList());
return runInContext(buildUserSecurityContext(tenant, username, authorities), tenantRunner::run);
}