diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f05b10c72..7ce412d8e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ on: env: NODE_VERSION: 22 - JAVA_VERSION: 21 + JAVA_VERSION: 25 defaults: run: diff --git a/.github/workflows/dependabot-frontend-build.yml b/.github/workflows/dependabot-frontend-build.yml index da72b6579..7b95847f0 100644 --- a/.github/workflows/dependabot-frontend-build.yml +++ b/.github/workflows/dependabot-frontend-build.yml @@ -18,7 +18,7 @@ defaults: env: NODE_VERSION: 22 - JAVA_VERSION: 21 + JAVA_VERSION: 25 jobs: test: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4908fd7..08d6247d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated Keycloak to 26.6.3 -- Update Quarkus to 3.27.3.1 LTS +- Updated to Java 25 +- Update Quarkus to 3.33.2.1 LTS - Improved browser locale detection (#371) - Improved efficiency of keycloak-to-hub data sync (#377) - Improved efficiency of group-based access permission checks (#372) diff --git a/backend/.idea/misc.xml b/backend/.idea/misc.xml index e8e9dd894..9bcac8d42 100644 --- a/backend/.idea/misc.xml +++ b/backend/.idea/misc.xml @@ -8,7 +8,26 @@ - + + + + + diff --git a/backend/pom.xml b/backend/pom.xml index 8b8ba1eb1..e2a560bb6 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -8,10 +8,11 @@ UTF-8 - 21 + 25 UTF-8 - 3.27.3.1 + 3.33.2.1 4.5.1 + 1.0.0 3.15.0 3.10.0 3.5.5 @@ -22,8 +23,8 @@ - io.quarkus - quarkus-universe-bom + io.quarkus.platform + quarkus-bom ${quarkus.platform.version} pom import @@ -98,12 +99,12 @@ io.quarkus - quarkus-junit5 + quarkus-junit test io.quarkus - quarkus-junit5-mockito + quarkus-junit-mockito test @@ -134,6 +135,11 @@ io.quarkus quarkus-opentelemetry + + org.jspecify + jspecify + ${jspecify.version} + @@ -183,7 +189,7 @@ - -javaagent:${org.mockito:mockito-core:jar} --add-opens java.base/java.lang=ALL-UNNAMED + @{argLine} -javaagent:${org.mockito:mockito-core:jar} --add-opens java.base/java.lang=ALL-UNNAMED org.jboss.logmanager.LogManager ${maven.home} @@ -215,7 +221,7 @@ - -javaagent:${org.mockito:mockito-core:jar} --add-opens java.base/java.lang=ALL-UNNAMED + @{argLine} -javaagent:${org.mockito:mockito-core:jar} --add-opens java.base/java.lang=ALL-UNNAMED plain false diff --git a/backend/src/main/docker/Dockerfile.jvm b/backend/src/main/docker/Dockerfile.jvm index a422607ee..290f47c8e 100644 --- a/backend/src/main/docker/Dockerfile.jvm +++ b/backend/src/main/docker/Dockerfile.jvm @@ -1,5 +1,5 @@ ## Stage 1 : build with maven builder image -FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21 AS builder +FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-25 AS builder COPY --chown=quarkus:quarkus --chmod=0755 mvnw /code/mvnw COPY --chown=quarkus:quarkus .mvn /code/.mvn COPY --chown=quarkus:quarkus pom.xml /code/ @@ -10,7 +10,7 @@ COPY src /code/src RUN ./mvnw -B package -DskipTests --no-transfer-progress --strict-checksums ## Stage 2 : create the docker final image -FROM eclipse-temurin:21 +FROM eclipse-temurin:25 COPY --from=builder /code/target/quarkus-app/ /work/ WORKDIR /work/ diff --git a/backend/src/main/docker/Dockerfile.native b/backend/src/main/docker/Dockerfile.native index 576b825e4..9adcd4912 100644 --- a/backend/src/main/docker/Dockerfile.native +++ b/backend/src/main/docker/Dockerfile.native @@ -1,5 +1,5 @@ ## Stage 1 : build with maven builder image with native capabilities -FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21 AS builder +FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-25 AS builder COPY --chown=quarkus:quarkus --chmod=0755 mvnw /code/mvnw COPY --chown=quarkus:quarkus .mvn /code/.mvn COPY --chown=quarkus:quarkus pom.xml /code/ diff --git a/backend/src/main/java/org/cryptomator/hub/Main.java b/backend/src/main/java/org/cryptomator/hub/Main.java index f1cbcca98..1f58da97f 100644 --- a/backend/src/main/java/org/cryptomator/hub/Main.java +++ b/backend/src/main/java/org/cryptomator/hub/Main.java @@ -12,8 +12,12 @@ public class Main implements QuarkusApplication { private static final Logger LOG = Logger.getLogger(Main.class); + private final LicenseHolder license; + @Inject - LicenseHolder license; + Main(LicenseHolder license) { + this.license = license; + } @Override public int run(String... args) throws Exception { diff --git a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java index 95571cbb8..de2a30888 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -56,10 +56,14 @@ public class AuditLogResource { VaultKeyRetrievedEvent.TYPE, VaultMemberAddedEvent.TYPE, VaultMemberRemovedEvent.TYPE, VaultMemberUpdatedEvent.TYPE, VaultOwnershipClaimedEvent.TYPE, EmergencyAccessSetupEvent.TYPE, EmergencyAccessSettingsUpdatedEvent.TYPE, EmergencyAccessRecoveryStartedEvent.TYPE, EmergencyAccessRecoveryApprovedEvent.TYPE, EmergencyAccessRecoveryCompletedEvent.TYPE, EmergencyAccessRecoveryAbortedEvent.TYPE); + private final AuditEvent.Repository auditEventRepo; + private final LicenseHolder license; + @Inject - AuditEvent.Repository auditEventRepo; - @Inject - LicenseHolder license; + AuditLogResource(AuditEvent.Repository auditEventRepo, LicenseHolder license) { + this.auditEventRepo = auditEventRepo; + this.license = license; + } @GET @RolesAllowed("admin") diff --git a/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java b/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java index 1ac651e78..242b57b4b 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java @@ -5,6 +5,7 @@ import org.cryptomator.hub.entities.Authority; import org.cryptomator.hub.entities.Group; import org.cryptomator.hub.entities.User; +import org.jspecify.annotations.Nullable; @JsonInclude(JsonInclude.Include.NON_NULL) abstract sealed class AuthorityDto permits UserDto, GroupDto, MemberDto { @@ -23,9 +24,9 @@ public enum Type { public final String name; @JsonProperty("pictureUrl") - public final String pictureUrl; + public final @Nullable String pictureUrl; - protected AuthorityDto(String id, Type type, String name, String pictureUrl) { + protected AuthorityDto(String id, Type type, String name, @Nullable String pictureUrl) { this.id = id; this.type = type; this.name = name; diff --git a/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java b/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java index e5c8564a1..7feb67bd0 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java @@ -21,8 +21,12 @@ @Produces(MediaType.TEXT_PLAIN) public class AuthorityResource { + private final Authority.Repository authorityRepo; + @Inject - Authority.Repository authorityRepo; + AuthorityResource(Authority.Repository authorityRepo) { + this.authorityRepo = authorityRepo; + } @GET @Path("/search") diff --git a/backend/src/main/java/org/cryptomator/hub/api/BillingResource.java b/backend/src/main/java/org/cryptomator/hub/api/BillingResource.java index 5f7bbe5f7..810be3c9b 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/BillingResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/BillingResource.java @@ -15,7 +15,6 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.EffectiveVaultAccess; -import org.cryptomator.hub.entities.Settings; import org.cryptomator.hub.license.LicenseHolder; import org.cryptomator.hub.validation.ValidJWS; import org.eclipse.microprofile.openapi.annotations.Operation; @@ -27,12 +26,14 @@ @Path("/billing") public class BillingResource { + private final LicenseHolder licenseHolder; + private final EffectiveVaultAccess.Repository effectiveVaultAccessRepo; + @Inject - LicenseHolder licenseHolder; - @Inject - EffectiveVaultAccess.Repository effectiveVaultAccessRepo; - @Inject - Settings.Repository settingsRepo; + BillingResource(LicenseHolder licenseHolder, EffectiveVaultAccess.Repository effectiveVaultAccessRepo) { + this.licenseHolder = licenseHolder; + this.effectiveVaultAccessRepo = effectiveVaultAccessRepo; + } @GET @Path("/") @@ -61,7 +62,7 @@ public Response setToken(@NotNull @ValidJWS String token) { try { licenseHolder.set(token); return Response.status(Response.Status.NO_CONTENT).build(); - } catch (JWTVerificationException e) { + } catch (JWTVerificationException _) { return Response.status(Response.Status.BAD_REQUEST).build(); } } diff --git a/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java b/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java index 3b804895e..a60a23178 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java @@ -18,35 +18,33 @@ @Path("/config") public class ConfigResource { - @Inject - @ConfigProperty(name = "hub.keycloak.public-url", defaultValue = "") - String keycloakPublicUrl; - - @Inject - @ConfigProperty(name = "hub.keycloak.realm", defaultValue = "") - String keycloakRealm; - - @Inject - @ConfigProperty(name = "quarkus.oidc.client-id", defaultValue = "") - String keycloakClientIdHub; - - @Inject - @ConfigProperty(name = "hub.keycloak.oidc.cryptomator-client-id", defaultValue = "") - String keycloakClientIdCryptomator; + private final String keycloakPublicUrl; + private final String keycloakRealm; + private final String keycloakClientIdHub; + private final String keycloakClientIdCryptomator; + private final String internalRealmUrl; + private final String billingUrl; + private final OidcConfigurationMetadata oidcConfData; + private final LicenseHolder license; @Inject - @ConfigProperty(name = "quarkus.oidc.auth-server-url") - String internalRealmUrl; - - @Inject - @ConfigProperty(name = "hub.billing-url", defaultValue = "") - String billingUrl; - - @Inject - OidcConfigurationMetadata oidcConfData; - - @Inject - LicenseHolder license; + ConfigResource(@ConfigProperty(name = "hub.keycloak.public-url", defaultValue = "") String keycloakPublicUrl, + @ConfigProperty(name = "hub.keycloak.realm", defaultValue = "") String keycloakRealm, + @ConfigProperty(name = "quarkus.oidc.client-id", defaultValue = "") String keycloakClientIdHub, + @ConfigProperty(name = "hub.keycloak.oidc.cryptomator-client-id", defaultValue = "") String keycloakClientIdCryptomator, + @ConfigProperty(name = "quarkus.oidc.auth-server-url") String internalRealmUrl, + @ConfigProperty(name = "hub.billing-url", defaultValue = "") String billingUrl, + OidcConfigurationMetadata oidcConfData, + LicenseHolder license) { + this.keycloakPublicUrl = keycloakPublicUrl; + this.keycloakRealm = keycloakRealm; + this.keycloakClientIdHub = keycloakClientIdHub; + this.keycloakClientIdCryptomator = keycloakClientIdCryptomator; + this.internalRealmUrl = internalRealmUrl; + this.billingUrl = billingUrl; + this.oidcConfData = oidcConfData; + this.license = license; + } @PermitAll @GET @@ -61,7 +59,7 @@ public ConfigDto getConfig() { } //visible for testing - String replacePrefix(String str, String prefix, String replacement) { + static String replacePrefix(String str, String prefix, String replacement) { int index = str.indexOf(prefix); if (index == 0) { return replacement + str.substring(prefix.length()); @@ -71,7 +69,7 @@ String replacePrefix(String str, String prefix, String replacement) { } //visible for testing - String trimTrailingSlash(String str) { + static String trimTrailingSlash(String str) { if (str.endsWith("/")) { return str.substring(0, str.length() - 1); } else { diff --git a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java index 116fd188e..dac972ea0 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java @@ -1,7 +1,7 @@ package org.cryptomator.hub.api; import com.fasterxml.jackson.annotation.JsonProperty; -import jakarta.annotation.Nullable; +import org.jspecify.annotations.Nullable; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.persistence.NoResultException; @@ -54,27 +54,31 @@ public class DeviceResource { private static final Logger LOG = Logger.getLogger(DeviceResource.class); - @Inject - EventLogger eventLogger; - @Inject - User.Repository userRepo; - @Inject - Device.Repository deviceRepo; + private final EventLogger eventLogger; + private final User.Repository userRepo; + private final Device.Repository deviceRepo; /** * @deprecated to be removed in #333 */ @Deprecated(since = "1.3.0", forRemoval = true) - @Inject - LegacyAccessToken.Repository legacyAccessTokenRepo; + private final LegacyAccessToken.Repository legacyAccessTokenRepo; /** * @deprecated to be removed in #333 */ @Deprecated(since = "1.3.0", forRemoval = true) - @Inject - LegacyDevice.Repository legacyDeviceRepo; + private final LegacyDevice.Repository legacyDeviceRepo; + private final JsonWebToken jwt; @Inject - JsonWebToken jwt; + @SuppressWarnings("deprecation") + DeviceResource(EventLogger eventLogger, User.Repository userRepo, Device.Repository deviceRepo, LegacyAccessToken.Repository legacyAccessTokenRepo, LegacyDevice.Repository legacyDeviceRepo, JsonWebToken jwt) { + this.eventLogger = eventLogger; + this.userRepo = userRepo; + this.deviceRepo = deviceRepo; + this.legacyAccessTokenRepo = legacyAccessTokenRepo; + this.legacyDeviceRepo = legacyDeviceRepo; + this.jwt = jwt; + } @GET @Path("/") @@ -130,7 +134,7 @@ public Response createOrUpdate(@Valid @NotNull DeviceDto dto, @PathParam("device Device device; try { device = deviceRepo.findByIdAndUser(deviceId, jwt.getSubject()); - } catch (NoResultException e) { + } catch (NoResultException _) { device = new Device(); device.setId(deviceId); device.setOwner(userRepo.findById(jwt.getSubject())); @@ -250,8 +254,8 @@ public record DeviceDto(@JsonProperty("id") @ValidId String id, @JsonProperty("userPrivateKey") @NotNull @ValidJWE String userPrivateKeys, // singular name for history reasons (don't break client compatibility) @JsonProperty("owner") @ValidId String ownerId, @JsonProperty("creationTime") Instant creationTime, - @JsonProperty("lastIpAddress") String lastIpAddress, - @JsonProperty("lastAccessTime") Instant lastAccessTime, + @JsonProperty("lastIpAddress") @Nullable String lastIpAddress, + @JsonProperty("lastAccessTime") @Nullable Instant lastAccessTime, @JsonProperty("legacyDevice") boolean legacyDevice) { public static DeviceDto fromEntity(Device entity) { @@ -263,7 +267,7 @@ public static DeviceDto fromEntity(Device entity) { */ @Deprecated(since = "1.3.0", forRemoval = true) public static DeviceDto fromEntity(LegacyDevice entity) { - return new DeviceDto(entity.getId(), entity.getName(), entity.getType(), entity.getPublickey(), null, entity.getOwner().getId(), entity.getCreationTime().truncatedTo(ChronoUnit.MILLIS), null, null, true); + return new DeviceDto(entity.getId(), entity.getName(), entity.getType(), entity.getPublickey(), null /* userPrivateKeys: intentionally null — legacy devices have none; the @NotNull only guards deserialization */, entity.getOwner().getId(), entity.getCreationTime().truncatedTo(ChronoUnit.MILLIS), null, null, true); } /** @@ -273,7 +277,7 @@ public static DeviceDto fromEntity(LegacyDevice entity) { public static DeviceDto fromEntity(LegacyDevice d, @Nullable VaultKeyRetrievedEvent event) { var lastIpAddress = (event != null) ? event.getIpAddress() : null; var lastAccessTime = (event != null) ? event.getTimestamp() : null; - return new DeviceResource.DeviceDto(d.getId(), d.getName(), d.getType(), d.getPublickey(), null, d.getOwner().getId(), d.getCreationTime().truncatedTo(ChronoUnit.MILLIS), lastIpAddress, lastAccessTime, true); + return new DeviceResource.DeviceDto(d.getId(), d.getName(), d.getType(), d.getPublickey(), null /* userPrivateKeys: intentionally null — legacy devices have none; the @NotNull only guards deserialization */, d.getOwner().getId(), d.getCreationTime().truncatedTo(ChronoUnit.MILLIS), lastIpAddress, lastAccessTime, true); } } diff --git a/backend/src/main/java/org/cryptomator/hub/api/EmergencyAccessResource.java b/backend/src/main/java/org/cryptomator/hub/api/EmergencyAccessResource.java index 2c31e49e0..31b75bffa 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/EmergencyAccessResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/EmergencyAccessResource.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import io.vertx.core.http.HttpServerRequest; +import org.jspecify.annotations.Nullable; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.transaction.Transactional; @@ -25,7 +26,6 @@ import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.EmergencyRecoveryProcess; import org.cryptomator.hub.entities.RecoveredEmergencyKeyShares; -import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.events.EventLogger; import org.cryptomator.hub.util.RawJson; import org.cryptomator.hub.validation.ValidJWE; @@ -42,23 +42,21 @@ @Path("/emergency-access") public class EmergencyAccessResource { - @Inject - EmergencyRecoveryProcess.Repository recoverProcessRepo; - - @Inject - RecoveredEmergencyKeyShares.Repository recoveredKeySharesRepo; - - @Inject - Vault.Repository vaultRepo; - - @Inject - JsonWebToken jwt; + private final EmergencyRecoveryProcess.Repository recoverProcessRepo; + private final RecoveredEmergencyKeyShares.Repository recoveredKeySharesRepo; + private final JsonWebToken jwt; + private final EventLogger eventLogger; @Context HttpServerRequest request; @Inject - EventLogger eventLogger; + EmergencyAccessResource(EmergencyRecoveryProcess.Repository recoverProcessRepo, RecoveredEmergencyKeyShares.Repository recoveredKeySharesRepo, JsonWebToken jwt, EventLogger eventLogger) { + this.recoverProcessRepo = recoverProcessRepo; + this.recoveredKeySharesRepo = recoveredKeySharesRepo; + this.jwt = jwt; + this.eventLogger = eventLogger; + } @PUT @Path("/{processId}") @@ -195,7 +193,7 @@ public record RecoveryProcessDto( @JsonProperty("id") @NotNull UUID id, @JsonProperty("vaultId") @NotNull UUID vaultId, @JsonProperty("type") @NotNull EmergencyRecoveryProcess.Type type, - @JsonProperty("details") @RawJson String details, + @JsonProperty("details") @RawJson @Nullable String details, @JsonProperty("requiredKeyShares") @Min(2) int requiredKeyShares, @JsonProperty("processPublicKey") @NotNull String processPublicKey, @JsonProperty("recoveredKeyShares") @NotEmpty Map recoveredKeyShares) { @@ -216,7 +214,7 @@ public static RecoveryProcessDto fromEntity(EmergencyRecoveryProcess entity) { @JsonInclude(JsonInclude.Include.NON_NULL) public record RecoveredKeyShareDto(@JsonProperty("processPrivateKey") @ValidJWE String processPrivateKey, @JsonProperty("unrecoveredKeyShare") @ValidJWE String unrecoveredKeyShare, - @JsonProperty("recoveredKeyShare") @ValidJWE String recoveredKeyShare, @JsonProperty("signedProcessInfo") @ValidJWS String signedProcessInfo) { + @JsonProperty("recoveredKeyShare") @ValidJWE @Nullable String recoveredKeyShare, @JsonProperty("signedProcessInfo") @ValidJWS @Nullable String signedProcessInfo) { public static RecoveredKeyShareDto fromEntity(RecoveredEmergencyKeyShares entity) { return new RecoveredKeyShareDto(entity.getProcessPrivateKey(), entity.getUnrecoveredKeyShare(), entity.getRecoveredKeyShare(), entity.getSignedProcessInfo()); diff --git a/backend/src/main/java/org/cryptomator/hub/api/GroupDto.java b/backend/src/main/java/org/cryptomator/hub/api/GroupDto.java index 9b5d6a686..5b48ec594 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/GroupDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/GroupDto.java @@ -3,18 +3,19 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonUnwrapped; import org.cryptomator.hub.entities.Group; +import org.jspecify.annotations.Nullable; import java.util.List; public final class GroupDto extends AuthorityDto { @JsonProperty("memberSize") - public final Integer memberSize; + public final @Nullable Integer memberSize; @JsonProperty("vaultCount") - public final Integer vaultCount; + public final @Nullable Integer vaultCount; - GroupDto(@JsonProperty("id") String id, @JsonProperty("name") String name, @JsonProperty("pictureUrl") String pictureUrl, @JsonProperty("memberSize") Integer memberSize, @JsonProperty("vaultCount") Integer vaultCount) { + GroupDto(@JsonProperty("id") String id, @JsonProperty("name") String name, @JsonProperty("pictureUrl") @Nullable String pictureUrl, @JsonProperty("memberSize") @Nullable Integer memberSize, @JsonProperty("vaultCount") @Nullable Integer vaultCount) { super(id, Type.GROUP, name, pictureUrl); this.memberSize = memberSize; this.vaultCount = vaultCount; diff --git a/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java b/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java index b8968c0aa..aaff7204c 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java @@ -1,6 +1,7 @@ package org.cryptomator.hub.api; import com.fasterxml.jackson.annotation.JsonProperty; +import org.jspecify.annotations.Nullable; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.transaction.Transactional; @@ -32,14 +33,18 @@ @Path("/groups") public class GroupsResource { + private final User.Repository userRepo; + private final Group.Repository groupRepo; + private final VaultAccess.Repository vaultAccessRepo; + private final KeycloakAuthorityPuller keycloakAuthorityPuller; + @Inject - User.Repository userRepo; - @Inject - Group.Repository groupRepo; - @Inject - VaultAccess.Repository vaultAccessRepo; - @Inject - KeycloakAuthorityPuller keycloakAuthorityPuller; + GroupsResource(User.Repository userRepo, Group.Repository groupRepo, VaultAccess.Repository vaultAccessRepo, KeycloakAuthorityPuller keycloakAuthorityPuller) { + this.userRepo = userRepo; + this.groupRepo = groupRepo; + this.vaultAccessRepo = vaultAccessRepo; + this.keycloakAuthorityPuller = keycloakAuthorityPuller; + } @GET @Path("/") @@ -172,14 +177,14 @@ public Response deleteGroup(@PathParam("groupId") @ValidId String groupId) { public record CreateGroupDto( @JsonProperty("name") @NotNull String name, - @JsonProperty("pictureUrl") @Size(max = 255) String pictureUrl + @JsonProperty("pictureUrl") @Size(max = 255) @Nullable String pictureUrl ) { } public record UpdateGroupDto( @JsonProperty("name") @NotNull String name, - @JsonProperty("pictureUrl") @Size(max = 255) String pictureUrl + @JsonProperty("pictureUrl") @Size(max = 255) @Nullable String pictureUrl ) { } } \ No newline at end of file diff --git a/backend/src/main/java/org/cryptomator/hub/api/LicenseResource.java b/backend/src/main/java/org/cryptomator/hub/api/LicenseResource.java index d4d08b202..f594fd059 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/LicenseResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/LicenseResource.java @@ -21,11 +21,14 @@ @Path("/license") public class LicenseResource { - @Inject - LicenseHolder licenseHolder; + private final LicenseHolder licenseHolder; + private final EffectiveVaultAccess.Repository effectiveVaultAccessRepo; @Inject - EffectiveVaultAccess.Repository effectiveVaultAccessRepo; + LicenseResource(LicenseHolder licenseHolder, EffectiveVaultAccess.Repository effectiveVaultAccessRepo) { + this.licenseHolder = licenseHolder; + this.effectiveVaultAccessRepo = effectiveVaultAccessRepo; + } @GET @Path("/user-info") diff --git a/backend/src/main/java/org/cryptomator/hub/api/MemberDto.java b/backend/src/main/java/org/cryptomator/hub/api/MemberDto.java index 04a8eb420..c74f9d3a5 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/MemberDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/MemberDto.java @@ -4,19 +4,20 @@ import org.cryptomator.hub.entities.Group; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.VaultAccess; +import org.jspecify.annotations.Nullable; public final class MemberDto extends AuthorityDto { @JsonProperty("ecdhPublicKey") - public final String ecdhPublicKey; + public final @Nullable String ecdhPublicKey; @JsonProperty("ecdsaPublicKey") - public final String ecdsaPublicKey; + public final @Nullable String ecdsaPublicKey; @JsonProperty("vaultRole") public final VaultAccess.Role role; @JsonProperty("memberSize") - public final Integer memberSize; + public final @Nullable Integer memberSize; - MemberDto(@JsonProperty("id") String id, @JsonProperty("type") Type type, @JsonProperty("name") String name, @JsonProperty("pictureUrl") String pictureUrl, @JsonProperty("ecdhPublicKey") String ecdhPublicKey, @JsonProperty("ecdsaPublicKey") String ecdsaPublicKey, @JsonProperty("vaultRole") VaultAccess.Role role, @JsonProperty("memberSize") Integer memberSize) { + MemberDto(@JsonProperty("id") String id, @JsonProperty("type") Type type, @JsonProperty("name") String name, @JsonProperty("pictureUrl") @Nullable String pictureUrl, @JsonProperty("ecdhPublicKey") @Nullable String ecdhPublicKey, @JsonProperty("ecdsaPublicKey") @Nullable String ecdsaPublicKey, @JsonProperty("vaultRole") VaultAccess.Role role, @JsonProperty("memberSize") @Nullable Integer memberSize) { super(id, type, name, pictureUrl); this.ecdhPublicKey = ecdhPublicKey; this.ecdsaPublicKey = ecdsaPublicKey; diff --git a/backend/src/main/java/org/cryptomator/hub/api/SettingsResource.java b/backend/src/main/java/org/cryptomator/hub/api/SettingsResource.java index 32a6af153..5256311f3 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/SettingsResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/SettingsResource.java @@ -27,14 +27,16 @@ @Path("/settings") public class SettingsResource { - @Inject - EventLogger eventLogger; - - @Inject - Settings.Repository settingsRepo; + private final EventLogger eventLogger; + private final Settings.Repository settingsRepo; + private final JsonWebToken jwt; @Inject - JsonWebToken jwt; + SettingsResource(EventLogger eventLogger, Settings.Repository settingsRepo, JsonWebToken jwt) { + this.eventLogger = eventLogger; + this.settingsRepo = settingsRepo; + this.jwt = jwt; + } @GET @RolesAllowed("user") diff --git a/backend/src/main/java/org/cryptomator/hub/api/UserDto.java b/backend/src/main/java/org/cryptomator/hub/api/UserDto.java index 6da9f8465..15c93463d 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UserDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UserDto.java @@ -4,11 +4,11 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonUnwrapped; -import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotNull; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.validation.OnlyBase64Chars; import org.cryptomator.hub.validation.ValidJWE; +import org.jspecify.annotations.Nullable; import java.util.List; import java.util.Set; @@ -17,25 +17,25 @@ public final class UserDto extends AuthorityDto { private final String email; - private final String firstName; - private final String lastName; - private final String language; + private final @Nullable String firstName; + private final @Nullable String lastName; + private final @Nullable String language; private final boolean enabled; private final Set devices; - private final String ecdhPublicKey; - private final String ecdsaPublicKey; - private final String privateKeys; - private final String setupCode; + private final @Nullable String ecdhPublicKey; + private final @Nullable String ecdsaPublicKey; + private final @Nullable String privateKeys; + private final @Nullable String setupCode; @JsonCreator public UserDto( @JsonProperty("id") @NotNull String id, @JsonProperty("name") @NotNull String name, - @JsonProperty("pictureUrl") String pictureUrl, + @JsonProperty("pictureUrl") @Nullable String pictureUrl, @JsonProperty("email") @NotNull String email, - @JsonProperty("firstName") String firstName, - @JsonProperty("lastName") String lastName, - @JsonProperty("language") String language, + @JsonProperty("firstName") @Nullable String firstName, + @JsonProperty("lastName") @Nullable String lastName, + @JsonProperty("language") @Nullable String language, @JsonProperty("enabled") boolean enabled, @JsonProperty("devices") Set devices, // Accept either "ecdhPublicKey" or the legacy "publicKey" on input @@ -62,17 +62,17 @@ public UserDto( public UserDto( String id, String name, - String pictureUrl, + @Nullable String pictureUrl, String email, - String firstName, - String lastName, - String language, + @Nullable String firstName, + @Nullable String lastName, + @Nullable String language, boolean enabled, Set devices, - String ecdhPublicKey, - String ecdsaPublicKey, - String privateKeys, - String setupCode) { + @Nullable String ecdhPublicKey, + @Nullable String ecdsaPublicKey, + @Nullable String privateKeys, + @Nullable String setupCode) { this(id, name, pictureUrl, email, firstName, lastName, language, enabled, devices, ecdhPublicKey, ecdhPublicKey, ecdsaPublicKey, privateKeys, privateKeys, setupCode); } @@ -82,17 +82,17 @@ public String getEmail() { } @JsonProperty("firstName") - public String getFirstName() { + public @Nullable String getFirstName() { return firstName; } @JsonProperty("lastName") - public String getLastName() { + public @Nullable String getLastName() { return lastName; } @JsonProperty("language") - public String getLanguage() { + public @Nullable String getLanguage() { return language; } @@ -107,7 +107,7 @@ public Set getDevices() { } @JsonProperty("ecdhPublicKey") - public String getEcdhPublicKey() { + public @Nullable String getEcdhPublicKey() { return ecdhPublicKey; } @@ -118,17 +118,17 @@ public String getEcdhPublicKey() { */ @Deprecated(forRemoval = true) @JsonProperty("publicKey") - public String getPublicKey() { + public @Nullable String getPublicKey() { return ecdhPublicKey; } @JsonProperty("ecdsaPublicKey") - public String getEcdsaPublicKey() { + public @Nullable String getEcdsaPublicKey() { return ecdsaPublicKey; } @JsonProperty("privateKeys") - public String getPrivateKeys() { + public @Nullable String getPrivateKeys() { return privateKeys; } @@ -139,12 +139,12 @@ public String getPrivateKeys() { */ @Deprecated(forRemoval = true) @JsonProperty("privateKey") - public String getPrivateKey() { + public @Nullable String getPrivateKey() { return privateKeys; } @JsonProperty("setupCode") - public String getSetupCode() { + public @Nullable String getSetupCode() { return setupCode; } diff --git a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java index 49d503b1a..78ff297cb 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java @@ -1,7 +1,7 @@ package org.cryptomator.hub.api; import com.fasterxml.jackson.annotation.JsonProperty; -import jakarta.annotation.Nullable; +import org.jspecify.annotations.Nullable; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.transaction.Transactional; @@ -53,30 +53,32 @@ @Produces(MediaType.TEXT_PLAIN) public class UsersResource { - @Inject - AccessToken.Repository accessTokenRepo; - @Inject - EventLogger eventLogger; - @Inject - User.Repository userRepo; - @Inject - Device.Repository deviceRepo; - @Inject - Vault.Repository vaultRepo; - @Inject - EmergencyRecoveryProcess.Repository emergencyRecovery; - @Inject - WotEntry.Repository wotRepo; - @Inject - EffectiveWot.Repository effectiveWotRepo; - @Inject - AuditEvent.Repository auditEventRepo; + private final AccessToken.Repository accessTokenRepo; + private final EventLogger eventLogger; + private final User.Repository userRepo; + private final Device.Repository deviceRepo; + private final Vault.Repository vaultRepo; + private final EmergencyRecoveryProcess.Repository emergencyRecovery; + private final WotEntry.Repository wotRepo; + private final EffectiveWot.Repository effectiveWotRepo; + private final AuditEvent.Repository auditEventRepo; + private final JsonWebToken jwt; + private final KeycloakAuthorityPuller keycloakAuthorityPuller; @Inject - JsonWebToken jwt; - - @Inject - KeycloakAuthorityPuller keycloakAuthorityPuller; + UsersResource(AccessToken.Repository accessTokenRepo, EventLogger eventLogger, User.Repository userRepo, Device.Repository deviceRepo, Vault.Repository vaultRepo, EmergencyRecoveryProcess.Repository emergencyRecovery, WotEntry.Repository wotRepo, EffectiveWot.Repository effectiveWotRepo, AuditEvent.Repository auditEventRepo, JsonWebToken jwt, KeycloakAuthorityPuller keycloakAuthorityPuller) { + this.accessTokenRepo = accessTokenRepo; + this.eventLogger = eventLogger; + this.userRepo = userRepo; + this.deviceRepo = deviceRepo; + this.vaultRepo = vaultRepo; + this.emergencyRecovery = emergencyRecovery; + this.wotRepo = wotRepo; + this.effectiveWotRepo = effectiveWotRepo; + this.auditEventRepo = auditEventRepo; + this.jwt = jwt; + this.keycloakAuthorityPuller = keycloakAuthorityPuller; + } @PUT @Path("/me") @@ -448,18 +450,18 @@ public record CreateUserDto( @JsonProperty("firstName") @NotNull String firstName, @JsonProperty("lastName") @NotNull String lastName, @JsonProperty("password") @NotNull String password, - @JsonProperty("pictureUrl") @Size(max = 255) String pictureUrl, - @JsonProperty("groupIds") Set groupIds, + @JsonProperty("pictureUrl") @Size(max = 255) @Nullable String pictureUrl, + @JsonProperty("groupIds") @Nullable Set groupIds, @JsonProperty("realmRoles") @NotNull Set realmRoles ) { } public record UpdateUserDto( - @JsonProperty("email") String email, - @JsonProperty("firstName") String firstName, - @JsonProperty("lastName") String lastName, - @JsonProperty("password") String password, - @JsonProperty("pictureUrl") @Size(max = 255) String pictureUrl, + @JsonProperty("email") @Nullable String email, + @JsonProperty("firstName") @Nullable String firstName, + @JsonProperty("lastName") @Nullable String lastName, + @JsonProperty("password") @Nullable String password, + @JsonProperty("pictureUrl") @Size(max = 255) @Nullable String pictureUrl, @JsonProperty("realmRoles") @NotNull Set realmRoles ) { } diff --git a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java index af47c7278..044267026 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -5,9 +5,8 @@ import com.auth0.jwt.exceptions.JWTVerificationException; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import io.quarkus.security.identity.SecurityIdentity; import io.vertx.core.http.HttpServerRequest; -import jakarta.annotation.Nullable; +import org.jspecify.annotations.Nullable; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.persistence.NoResultException; @@ -44,6 +43,7 @@ import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.VaultAccess; +import org.cryptomator.hub.entities.VaultAccess.Role; import org.cryptomator.hub.entities.events.EventLogger; import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; import org.cryptomator.hub.filters.ActiveLicense; @@ -79,62 +79,52 @@ @Path("/vaults") public class VaultResource { - @Inject - EventLogger eventLogger; - - @Inject - AccessToken.Repository accessTokenRepo; - - @Inject - Device.Repository deviceRepo; - - @Inject - Group.Repository groupRepo; - - @Inject - User.Repository userRepo; - - @Inject - Authority.Repository authorityRepo; - - @Inject - EffectiveVaultAccess.Repository effectiveVaultAccessRepo; - + private final EventLogger eventLogger; + private final AccessToken.Repository accessTokenRepo; + private final Device.Repository deviceRepo; + private final Group.Repository groupRepo; + private final User.Repository userRepo; + private final Authority.Repository authorityRepo; + private final EffectiveVaultAccess.Repository effectiveVaultAccessRepo; /** * @deprecated to be removed in #333 */ - @Inject @Deprecated(since = "1.3.0", forRemoval = true) - LegacyAccessToken.Repository legacyAccessTokenRepo; - - @Inject - Vault.Repository vaultRepo; - - @Inject - VaultAccess.Repository vaultAccessRepo; - - @Inject - JsonWebToken jwt; - - @Inject - SecurityIdentity identity; - - @Inject - LicenseHolder license; - - @Inject - VaultUnlockMetrics vaultUnlockMetrics; + private final LegacyAccessToken.Repository legacyAccessTokenRepo; + private final Vault.Repository vaultRepo; + private final VaultAccess.Repository vaultAccessRepo; + private final JsonWebToken jwt; + private final LicenseHolder license; + private final VaultUnlockMetrics vaultUnlockMetrics; @Context HttpServerRequest request; + @Inject + @SuppressWarnings("deprecation") + VaultResource(EventLogger eventLogger, AccessToken.Repository accessTokenRepo, Device.Repository deviceRepo, Group.Repository groupRepo, User.Repository userRepo, Authority.Repository authorityRepo, EffectiveVaultAccess.Repository effectiveVaultAccessRepo, LegacyAccessToken.Repository legacyAccessTokenRepo, Vault.Repository vaultRepo, VaultAccess.Repository vaultAccessRepo, JsonWebToken jwt, LicenseHolder license, VaultUnlockMetrics vaultUnlockMetrics) { + this.eventLogger = eventLogger; + this.accessTokenRepo = accessTokenRepo; + this.deviceRepo = deviceRepo; + this.groupRepo = groupRepo; + this.userRepo = userRepo; + this.authorityRepo = authorityRepo; + this.effectiveVaultAccessRepo = effectiveVaultAccessRepo; + this.legacyAccessTokenRepo = legacyAccessTokenRepo; + this.vaultRepo = vaultRepo; + this.vaultAccessRepo = vaultAccessRepo; + this.jwt = jwt; + this.license = license; + this.vaultUnlockMetrics = vaultUnlockMetrics; + } + @GET @Path("/accessible") @RolesAllowed("user") @Produces(MediaType.APPLICATION_JSON) @Transactional @Operation(summary = "list all accessible vaults", description = "list all vaults that have been shared with the currently logged in user or a group in wich this user is") - public List getAccessible(@Nullable @QueryParam("role") VaultAccess.Role role) { + public List getAccessible(@Nullable @QueryParam("role") Role role) { var currentUserId = jwt.getSubject(); final Stream resultStream; if (role == null) { @@ -239,7 +229,7 @@ public Response setDirectMembers(@PathParam("vaultId") UUID vaultId, @NotEmpty M // resolve group members and simulate new seat count: var effectiveUsers = new HashSet(); effectiveUsers.addAll(userRepo.getEffectiveGroupUsers(memberRoles.keySet())); - effectiveUsers.addAll(userRepo.findByIds(memberRoles.keySet()).toList()); + effectiveUsers.addAll(userRepo.streamByIds(memberRoles.keySet()).toList()); var newSeatOccupyingUsers = new HashSet<>(effectiveVaultAccessRepo.usersSeatedOnOtherVaults(vaultId).toList()); // initialize with users already having access to other vaults newSeatOccupyingUsers.addAll(effectiveUsers.stream().map(User::getId).toList()); // add all users that will have access to this vault after the operation (avoid double counting by using a set) if (newSeatOccupyingUsers.size() > license.getEntitlements().seats()) { @@ -403,7 +393,7 @@ public Response legacyUnlock(@PathParam("vaultId") UUID vaultId, @PathParam("dev response = response.header("Hub-Android-License", androidLicense); } return response.build(); - } catch (NoResultException e) { + } catch (NoResultException _) { eventLogger.logVaultKeyRetrieved(Instant.now(), jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED, ipAddress, deviceId); throw new ForbiddenException("Access to this device not granted."); } @@ -551,7 +541,7 @@ public VaultDto setArchived(@PathParam("vaultId") UUID vaultId, @NotNull Boolean .collect(Collectors.toSet()); var effectiveUsers = new HashSet(); effectiveUsers.addAll(userRepo.getEffectiveGroupUsers(authorityIds)); - effectiveUsers.addAll(userRepo.findByIds(authorityIds).toList()); + effectiveUsers.addAll(userRepo.streamByIds(authorityIds).toList()); var projectedSeatUsers = new HashSet<>(effectiveVaultAccessRepo.usersSeatedOnOtherVaults(vaultId).toList()); projectedSeatUsers.addAll(effectiveUsers.stream().map(User::getId).toList()); if (projectedSeatUsers.size() > license.getEntitlements().seats()) { @@ -687,14 +677,14 @@ public Response claimOwnership(@PathParam("vaultId") UUID vaultId, @FormParam("p @JsonInclude(JsonInclude.Include.NON_NULL) public record VaultDto(@JsonProperty("id") @NotNull UUID id, @JsonProperty("name") @NoHtmlOrScriptChars @NotBlank String name, - @JsonProperty("creationTime") Instant creationTime, @JsonProperty("description") @NoHtmlOrScriptChars String description, + @JsonProperty("creationTime") Instant creationTime, @JsonProperty("description") @NoHtmlOrScriptChars @Nullable String description, @JsonProperty("archived") boolean archived, @JsonProperty("requiredEmergencyKeyShares") @Min(0) int requiredEmergencyKeyShares, @JsonProperty("emergencyKeyShares") Map emergencyKeyShares, // Legacy properties ("Vault Admin Password"): - @JsonProperty("masterkey") @OnlyBase64Chars String masterkey, @JsonProperty("iterations") Integer iterations, - @JsonProperty("salt") @OnlyBase64Chars String salt, - @JsonProperty("authPublicKey") @OnlyBase64Chars String authPublicKey, @JsonProperty("authPrivateKey") @OnlyBase64Chars String authPrivateKey + @JsonProperty("masterkey") @OnlyBase64Chars @Nullable String masterkey, @JsonProperty("iterations") @Nullable Integer iterations, + @JsonProperty("salt") @OnlyBase64Chars @Nullable String salt, + @JsonProperty("authPublicKey") @OnlyBase64Chars @Nullable String authPublicKey, @JsonProperty("authPrivateKey") @OnlyBase64Chars @Nullable String authPrivateKey ) { public static VaultDto fromEntity(Vault entity) { @@ -706,7 +696,7 @@ public static VaultDto fromEntity(Vault entity) { public record VaultDtoWithRole( @JsonProperty("id") UUID id, @JsonProperty("name") String name, - @JsonProperty("description") String description, + @JsonProperty("description") @Nullable String description, @JsonProperty("archived") boolean archived, @JsonProperty("creationTime") Instant creationTime, @JsonProperty("role") VaultAccess.Role role diff --git a/backend/src/main/java/org/cryptomator/hub/api/VersionResource.java b/backend/src/main/java/org/cryptomator/hub/api/VersionResource.java index a9b724cb0..c032478db 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VersionResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VersionResource.java @@ -1,6 +1,7 @@ package org.cryptomator.hub.api; import com.fasterxml.jackson.annotation.JsonProperty; +import org.jspecify.annotations.Nullable; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -14,11 +15,14 @@ @Path("/version") public class VersionResource { - @ConfigProperty(name = "quarkus.application.version") - String hubVersion; + private final String hubVersion; + private final Keycloak keycloak; @Inject - Keycloak keycloak; + VersionResource(@ConfigProperty(name = "quarkus.application.version") String hubVersion, Keycloak keycloak) { + this.hubVersion = hubVersion; + this.keycloak = keycloak; + } @GET @Produces(MediaType.APPLICATION_JSON) @@ -33,7 +37,7 @@ public VersionDto getVersion() { return new VersionDto(hubVersion, keycloakVersion); } - public record VersionDto(@JsonProperty("hubVersion") String hubVersion, @JsonProperty("keycloakVersion") String keycloakVersion) { + public record VersionDto(@JsonProperty("hubVersion") String hubVersion, @JsonProperty("keycloakVersion") @Nullable String keycloakVersion) { } } diff --git a/backend/src/main/java/org/cryptomator/hub/api/package-info.java b/backend/src/main/java/org/cryptomator/hub/api/package-info.java new file mode 100644 index 000000000..dabced1af --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/api/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.cryptomator.hub.api; + +import org.jspecify.annotations.NullMarked; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java b/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java index 96720bc79..5b414f2ad 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java @@ -1,7 +1,6 @@ package org.cryptomator.hub.entities; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; @@ -15,6 +14,7 @@ import jakarta.persistence.NoResultException; import jakarta.persistence.Table; +import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -116,14 +116,14 @@ public static class Repository implements PanacheRepositoryBase { public Stream byName(String name) { - return find("#Authority.byName", Parameters.with("name", '%' + name.toLowerCase() + '%')).stream(); + return find("#Authority.byName", Map.of("name", '%' + name.toLowerCase() + '%')).stream(); } public Stream findAllInList(Collection ids) { - return Batch.of(200).run(ids, Stream.empty(), (batch, result) -> { - var partial = find("WHERE id IN :ids", Parameters.with("ids", batch)); - return Stream.concat(result, partial.stream()); - }); + return ids.stream() + .gather(Gatherers.windowFixed(200)) + .flatMap(batch -> find("WHERE id IN :ids", Map.of("ids", batch)).stream()); } } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/Batch.java b/backend/src/main/java/org/cryptomator/hub/entities/Batch.java index f3661e89f..c2c718ea0 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Batch.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Batch.java @@ -25,7 +25,7 @@ public static Batch of(int size) { // TODO: add jspecify annotations public void run(Collection collection, Consumer> job) { - run(collection, null, (batch, ignored) -> { + run(collection, null, (batch, _) -> { job.accept(batch); return null; }); diff --git a/backend/src/main/java/org/cryptomator/hub/entities/Device.java b/backend/src/main/java/org/cryptomator/hub/entities/Device.java index c7800ff4b..d01178700 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Device.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Device.java @@ -1,7 +1,6 @@ package org.cryptomator.hub.entities; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -17,7 +16,9 @@ import java.time.Instant; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.stream.Gatherers; import java.util.stream.Stream; @Entity @@ -180,14 +181,13 @@ public int hashCode() { public static class Repository implements PanacheRepositoryBase { public Device findByIdAndUser(String deviceId, String userId) throws NoResultException { - return find("#Device.findByIdAndOwner", Parameters.with("deviceId", deviceId).and("userId", userId)).singleResult(); + return find("#Device.findByIdAndOwner", Map.of("deviceId", deviceId, "userId", userId)).singleResult(); } public Stream findAllInList(List ids) { - return Batch.of(200).run(ids, Stream.empty(), (batch, result) -> { - var partial = find("#Device.allInList", Parameters.with("ids", batch)); - return Stream.concat(result, partial.stream()); - }); + return ids.stream() + .gather(Gatherers.windowFixed(200)) + .flatMap(batch -> find("#Device.allInList", Map.of("ids", batch)).stream()); } public void updateLastAccess(String deviceId, Instant timestamp, String ipAddress) { @@ -195,7 +195,7 @@ public void updateLastAccess(String deviceId, Instant timestamp, String ipAddres } public void deleteByOwner(String userId) { - delete("#Device.deleteByOwner", Parameters.with("userId", userId)); + delete("#Device.deleteByOwner", Map.of("userId", userId)); } } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java index 91a95da15..fccf6db90 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java @@ -2,7 +2,6 @@ import io.opentelemetry.instrumentation.annotations.WithSpan; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; @@ -14,6 +13,7 @@ import org.hibernate.annotations.Immutable; import java.util.Collection; +import java.util.Map; @NamedNativeQuery(name = "EffectiveGroupMembership.fullUpdate", query = """ INSERT INTO "effective_group_membership" ("group_id", "intermediate_group_ids", "member_id") @@ -105,7 +105,7 @@ public void updateGroups(Collection groupIds) { @WithSpan("EffectiveGroupMembership.Repository.updateUsers") public void updateUsers(Collection userIds) { Batch.of(200).run(userIds, batch -> { - delete("#EffectiveGroupMembership.deleteUsers", Parameters.with("userIds", batch)); + delete("#EffectiveGroupMembership.deleteUsers", Map.of("userIds", batch)); getEntityManager() .createNamedQuery("EffectiveGroupMembership.updateUsers") .setParameter("userIds", batch) diff --git a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java index 42b8c1713..be9761a31 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java @@ -2,7 +2,6 @@ import io.opentelemetry.instrumentation.annotations.WithSpan; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; @@ -19,6 +18,7 @@ import org.hibernate.annotations.Immutable; import java.util.Collection; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -118,12 +118,12 @@ public record Id( public static class Repository implements PanacheRepositoryBase { public boolean isUserOccupyingSeat(String userId) { - return find("#EffectiveVaultAccess.isUserOccupyingSeat", Parameters.with("userId", userId)).page(0, 1).firstResult() != null; + return find("#EffectiveVaultAccess.isUserOccupyingSeat", Map.of("userId", userId)).page(0, 1).firstResult() != null; } public long countSeatsOccupiedByUsers(Collection userIds) { return Batch.of(200).run(Set.copyOf(userIds), 0L, (batch, result) -> { - long partialCount = count("#EffectiveVaultAccess.countSeatsOccupiedByUsers", Parameters.with("userIds", batch)); + long partialCount = count("#EffectiveVaultAccess.countSeatsOccupiedByUsers", Map.of("userIds", batch)); return result + partialCount; }); } @@ -138,17 +138,17 @@ public long countSeatOccupyingUsersWithAccessToken() { } public long countSeatOccupyingUsersOfGroup(String groupId) { - return count("#EffectiveVaultAccess.countSeatOccupyingUsersOfGroup", Parameters.with("groupId", groupId)); + return count("#EffectiveVaultAccess.countSeatOccupyingUsersOfGroup", Map.of("groupId", groupId)); } public Collection listRoles(UUID vaultId, String authorityId) { - return find("#EffectiveVaultAccess.findByAuthorityAndVault", Parameters.with("vaultId", vaultId).and("authorityId", authorityId)).stream() + return find("#EffectiveVaultAccess.findByAuthorityAndVault", Map.of("vaultId", vaultId, "authorityId", authorityId)).stream() .map(eva -> eva.getId().role()) .collect(Collectors.toUnmodifiableSet()); } public Stream usersSeatedOnOtherVaults(UUID vaultId) { - return find("#EffectiveVaultAccess.usersSeatedOnOtherVaults", Parameters.with("vaultId", vaultId)).project(String.class).stream(); + return find("#EffectiveVaultAccess.usersSeatedOnOtherVaults", Map.of("vaultId", vaultId)).project(String.class).stream(); } } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveWot.java b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveWot.java index 7a70632bd..2a12c81f9 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveWot.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveWot.java @@ -2,7 +2,6 @@ import io.quarkus.hibernate.orm.panache.PanacheQuery; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; @@ -12,6 +11,7 @@ import jakarta.persistence.Table; import org.hibernate.annotations.Immutable; import org.hibernate.annotations.Type; +import java.util.Map; @Entity @Immutable @@ -31,7 +31,7 @@ public class EffectiveWot { @EmbeddedId private Id id; - @Column(name = "signature_chain") + @Column(name = "signature_chain", nullable = false) @Type(StringArrayType.class) private String[] signatureChain; @@ -60,11 +60,11 @@ public record Id( @ApplicationScoped public static class Repository implements PanacheRepositoryBase { public PanacheQuery findTrusted(String trustingUserId) { - return find("#EffectiveWot.findTrustedUsers", Parameters.with("trustingUserId", trustingUserId)); + return find("#EffectiveWot.findTrustedUsers", Map.of("trustingUserId", trustingUserId)); } public PanacheQuery findTrusted(String trustingUserId, String trustedUserId) { - return find("#EffectiveWot.findTrustedUser", Parameters.with("trustingUserId", trustingUserId).and("trustedUserId", trustedUserId)); + return find("#EffectiveWot.findTrustedUser", Map.of("trustingUserId", trustingUserId, "trustedUserId", trustedUserId)); } } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/EmergencyRecoveryProcess.java b/backend/src/main/java/org/cryptomator/hub/entities/EmergencyRecoveryProcess.java index cb820d175..f3062a9e7 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/EmergencyRecoveryProcess.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/EmergencyRecoveryProcess.java @@ -1,7 +1,6 @@ package org.cryptomator.hub.entities; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; @@ -14,6 +13,7 @@ import jakarta.persistence.NamedQuery; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.HashMap; import java.util.Map; @@ -68,7 +68,7 @@ public enum Type { private Type type; @Column(name = "details") - private String details; + private @Nullable String details; @Column(name = "required_key_shares", nullable = false) private int requiredKeyShares; @@ -104,11 +104,11 @@ public void setType(Type type) { this.type = type; } - public String getDetails() { + public @Nullable String getDetails() { return details; } - public void setDetails(String details) { + public void setDetails(@Nullable String details) { this.details = details; } @@ -153,15 +153,15 @@ public int hashCode() { public static class Repository implements PanacheRepositoryBase { public Stream findByVaultId(UUID vaultId) { - return find("#EmergencyRecoveryProcess.findByVaultId", Parameters.with("vaultId", vaultId)).stream(); + return find("#EmergencyRecoveryProcess.findByVaultId", Map.of("vaultId", vaultId)).stream(); } public Stream findByCouncilMember(String councilMemberId) { - return find("#EmergencyRecoveryProcess.byCouncilMember", Parameters.with("councilMemberId", councilMemberId)).stream(); + return find("#EmergencyRecoveryProcess.byCouncilMember", Map.of("councilMemberId", councilMemberId)).stream(); } public Stream findByCouncilMemberOrProcessMember(String councilMemberId) { - return find("#EmergencyRecoveryProcess.byCouncilMemberOrProcessMember", Parameters.with("councilMemberId", councilMemberId)).stream(); + return find("#EmergencyRecoveryProcess.byCouncilMemberOrProcessMember", Map.of("councilMemberId", councilMemberId)).stream(); } public void deleteKeySharesForCouncilMember(String councilMemberId) { diff --git a/backend/src/main/java/org/cryptomator/hub/entities/Group.java b/backend/src/main/java/org/cryptomator/hub/entities/Group.java index 398ae4877..ded3eb61e 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Group.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Group.java @@ -1,7 +1,6 @@ package org.cryptomator.hub.entities; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -18,6 +17,7 @@ import java.util.Collection; import java.util.HashSet; +import java.util.Map; import java.util.Set; @NamedNativeQuery(name = "Group.addMember", query = """ @@ -63,7 +63,7 @@ public Group findByIdWithEagerDetails(String id) { FROM Group g LEFT JOIN FETCH g.members WHERE g.id = :id - """, Parameters.with("id", id)).singleResultOptional().orElse(null); + """, Map.of("id", id)).singleResultOptional().orElse(null); if (group == null) { return null; } @@ -73,7 +73,7 @@ public Group findByIdWithEagerDetails(String id) { } public long deleteByIds(Collection ids) { - return Batch.of(200).run(ids, 0L, (batch, result) -> result + delete("id IN :ids", Parameters.with("ids", batch))); + return Batch.of(200).run(ids, 0L, (batch, result) -> result + delete("id IN :ids", Map.of("ids", batch))); } /** diff --git a/backend/src/main/java/org/cryptomator/hub/entities/LegacyDevice.java b/backend/src/main/java/org/cryptomator/hub/entities/LegacyDevice.java index 7f8c00f8f..0815298d1 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/LegacyDevice.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/LegacyDevice.java @@ -1,7 +1,6 @@ package org.cryptomator.hub.entities; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -16,6 +15,7 @@ import java.time.Instant; import java.util.List; +import java.util.Map; import java.util.stream.Stream; /** @@ -95,7 +95,7 @@ public String getPublickey() { public static class Repository implements PanacheRepositoryBase { public Stream findAllInList(List ids) { - return find("#LegacyDevice.allInList", Parameters.with("ids", ids)).stream(); + return find("#LegacyDevice.allInList", Map.of("ids", ids)).stream(); } public boolean existsAny() { @@ -103,7 +103,7 @@ public boolean existsAny() { } public void deleteByOwner(String userId) { - delete("#LegacyDevice.deleteByOwner", Parameters.with("userId", userId)); + delete("#LegacyDevice.deleteByOwner", Map.of("userId", userId)); } } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/RecoveredEmergencyKeyShares.java b/backend/src/main/java/org/cryptomator/hub/entities/RecoveredEmergencyKeyShares.java index a7866a884..24d5a05e6 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/RecoveredEmergencyKeyShares.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/RecoveredEmergencyKeyShares.java @@ -7,6 +7,7 @@ import jakarta.persistence.EmbeddedId; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; import java.util.UUID; @@ -25,10 +26,10 @@ public class RecoveredEmergencyKeyShares { private String unrecoveredKeyShare; @Column(name = "recovered_key_share") - private String recoveredKeyShare; + private @Nullable String recoveredKeyShare; @Column(name = "signed_process_info") - private String signedProcessInfo; + private @Nullable String signedProcessInfo; public Id getId() { return id; @@ -54,19 +55,19 @@ public void setUnrecoveredKeyShare(String unrecoveredKeyShare) { this.unrecoveredKeyShare = unrecoveredKeyShare; } - public String getRecoveredKeyShare() { + public @Nullable String getRecoveredKeyShare() { return recoveredKeyShare; } - public void setRecoveredKeyShare(String recoveredKeyShare) { + public void setRecoveredKeyShare(@Nullable String recoveredKeyShare) { this.recoveredKeyShare = recoveredKeyShare; } - public String getSignedProcessInfo() { + public @Nullable String getSignedProcessInfo() { return signedProcessInfo; } - public void setSignedProcessInfo(String signedProcessInfo) { + public void setSignedProcessInfo(@Nullable String signedProcessInfo) { this.signedProcessInfo = signedProcessInfo; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/Settings.java b/backend/src/main/java/org/cryptomator/hub/entities/Settings.java index 056868e70..0bf7d7ea1 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Settings.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Settings.java @@ -9,6 +9,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.HashSet; @@ -29,7 +30,7 @@ public class Settings { private String hubId; @Column(name = "license_key") - private String licenseKey; + private @Nullable String licenseKey; @Column(name = "wot_max_depth", nullable = false) private int wotMaxDepth; @@ -73,11 +74,11 @@ public void setHubId(String hubId) { this.hubId = hubId; } - public String getLicenseKey() { + public @Nullable String getLicenseKey() { return licenseKey; } - public void setLicenseKey(String licenseKey) { + public void setLicenseKey(@Nullable String licenseKey) { this.licenseKey = licenseKey; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/User.java b/backend/src/main/java/org/cryptomator/hub/entities/User.java index af1abd6e6..861b5f8a9 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/User.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/User.java @@ -2,7 +2,6 @@ import io.quarkus.hibernate.orm.panache.PanacheQuery; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; @@ -14,14 +13,17 @@ import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import org.hibernate.annotations.Immutable; +import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.Gatherers; import java.util.stream.Stream; @Entity @@ -50,31 +52,31 @@ SELECT count( DISTINCT u) public class User extends Authority { @Column(name = "email") - private String email; + private @Nullable String email; @Column(name = "firstname") - private String firstName; + private @Nullable String firstName; @Column(name = "lastname") - private String lastName; + private @Nullable String lastName; @Column(name = "language") - private String language; + private @Nullable String language; @Column(name = "enabled", nullable = false) private boolean enabled = true; @Column(name = "ecdh_publickey") - private String ecdhPublicKey; + private @Nullable String ecdhPublicKey; @Column(name = "ecdsa_publickey") - private String ecdsaPublicKey; + private @Nullable String ecdsaPublicKey; @Column(name = "privatekeys") - private String privateKeys; + private @Nullable String privateKeys; @Column(name = "setupcode") - private String setupCode; + private @Nullable String setupCode; @OneToOne(mappedBy = "user", fetch = FetchType.LAZY) public UserMetrics metrics; @@ -99,35 +101,35 @@ public class User extends Authority { @OneToMany(mappedBy = "owner", orphanRemoval = true, fetch = FetchType.LAZY) private Set legacyDevices = new HashSet<>(); - public String getEmail() { + public @Nullable String getEmail() { return email; } - public void setEmail(String email) { + public void setEmail(@Nullable String email) { this.email = email; } - public String getFirstName() { + public @Nullable String getFirstName() { return firstName; } - public void setFirstName(String firstName) { + public void setFirstName(@Nullable String firstName) { this.firstName = firstName; } - public String getLastName() { + public @Nullable String getLastName() { return lastName; } - public void setLastName(String lastName) { + public void setLastName(@Nullable String lastName) { this.lastName = lastName; } - public String getLanguage() { + public @Nullable String getLanguage() { return language; } - public void setLanguage(String language) { + public void setLanguage(@Nullable String language) { this.language = language; } @@ -139,35 +141,35 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } - public String getEcdhPublicKey() { + public @Nullable String getEcdhPublicKey() { return ecdhPublicKey; } - public void setEcdhPublicKey(String ecdhPublicKey) { + public void setEcdhPublicKey(@Nullable String ecdhPublicKey) { this.ecdhPublicKey = ecdhPublicKey; } - public String getEcdsaPublicKey() { + public @Nullable String getEcdsaPublicKey() { return ecdsaPublicKey; } - public void setEcdsaPublicKey(String ecdsaPublicKey) { + public void setEcdsaPublicKey(@Nullable String ecdsaPublicKey) { this.ecdsaPublicKey = ecdsaPublicKey; } - public String getPrivateKeys() { + public @Nullable String getPrivateKeys() { return privateKeys; } - public void setPrivateKeys(String privateKeys) { + public void setPrivateKeys(@Nullable String privateKeys) { this.privateKeys = privateKeys; } - public String getSetupCode() { + public @Nullable String getSetupCode() { return setupCode; } - public void setSetupCode(String setupCode) { + public void setSetupCode(@Nullable String setupCode) { this.setupCode = setupCode; } @@ -241,7 +243,7 @@ public User findByIdWithEagerDetails(String id) { LEFT JOIN FETCH u.devices d LEFT JOIN FETCH u.legacyDevices ld WHERE u.id = :id - """, Parameters.with("id", id)).singleResultOptional().orElse(null); + """, Map.of("id", id)).singleResultOptional().orElse(null); if (user == null) { return null; } @@ -258,23 +260,22 @@ public User findByIdWithEagerDetails(String id) { return user; } - public Stream findByIds(Collection ids) { - return Batch.of(200).run(ids, Stream.empty(), (batch, result) -> { - var partial = find("id IN :ids", Parameters.with("ids", batch)); - return Stream.concat(result, partial.stream()); - }); + public Stream streamByIds(Collection ids) { + return ids.stream() + .gather(Gatherers.windowFixed(200)) + .flatMap(batch -> find("id IN :ids", Map.of("ids", batch)).stream()); } public long deleteByIds(Collection ids) { - return Batch.of(200).run(ids, 0L, (batch, result) -> result + delete("id IN :ids", Parameters.with("ids", batch))); + return Batch.of(200).run(ids, 0L, (batch, result) -> result + delete("id IN :ids", Map.of("ids", batch))); } public Stream findRequiringAccessGrant(UUID vaultId) { - return find("#User.requiringAccessGrant", Parameters.with("vaultId", vaultId)).stream(); + return find("#User.requiringAccessGrant", Map.of("vaultId", vaultId)).stream(); } public long countEffectiveGroupUsers(String groupdId) { - return count("#User.countEffectiveGroupUsers", Parameters.with("groupId", groupdId)); + return count("#User.countEffectiveGroupUsers", Map.of("groupId", groupdId)); } public Stream getEffectiveGroupUsers(String groupdId) { @@ -283,7 +284,7 @@ public Stream getEffectiveGroupUsers(String groupdId) { public Set getEffectiveGroupUsers(Collection groupIds) { return Batch.of(200).run(groupIds, new HashSet<>(), (batch, result) -> { - var partial = find("#User.getEffectiveGroupUsers", Parameters.with("groupIds", batch)).project(User.class); + var partial = find("#User.getEffectiveGroupUsers", Map.of("groupIds", batch)).project(User.class); result.addAll(partial.list()); return result; }); diff --git a/backend/src/main/java/org/cryptomator/hub/entities/Vault.java b/backend/src/main/java/org/cryptomator/hub/entities/Vault.java index e34f82af7..5ddd802aa 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Vault.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Vault.java @@ -1,7 +1,6 @@ package org.cryptomator.hub.entities; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; @@ -18,6 +17,7 @@ import jakarta.persistence.Table; import jakarta.transaction.Transactional; import org.hibernate.annotations.Immutable; +import org.jspecify.annotations.Nullable; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; @@ -35,6 +35,7 @@ import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.Gatherers; import java.util.stream.Stream; @Entity @@ -91,25 +92,25 @@ public class Vault { private String name; @Column(name = "salt") - private String salt; + private @Nullable String salt; @Column(name = "iterations") - private Integer iterations; + private @Nullable Integer iterations; @Column(name = "masterkey") - private String masterkey; + private @Nullable String masterkey; @Column(name = "auth_pubkey") - private String authenticationPublicKey; + private @Nullable String authenticationPublicKey; @Column(name = "auth_prvkey") - private String authenticationPrivateKey; + private @Nullable String authenticationPrivateKey; @Column(name = "creation_time", nullable = false) private Instant creationTime; @Column(name = "description") - private String description; + private @Nullable String description; @Column(name = "archived", nullable = false) private boolean archived; @@ -140,7 +141,7 @@ public Optional getAuthenticationPublicKeyOptional() { } else { return Optional.empty(); } - } catch (InvalidKeySpecException e) { + } catch (InvalidKeySpecException _) { return Optional.empty(); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); @@ -175,43 +176,43 @@ public void setName(String name) { this.name = name; } - public String getSalt() { + public @Nullable String getSalt() { return salt; } - public void setSalt(String salt) { + public void setSalt(@Nullable String salt) { this.salt = salt; } - public Integer getIterations() { + public @Nullable Integer getIterations() { return iterations; } - public void setIterations(Integer iterations) { + public void setIterations(@Nullable Integer iterations) { this.iterations = iterations; } - public String getMasterkey() { + public @Nullable String getMasterkey() { return masterkey; } - public void setMasterkey(String masterkey) { + public void setMasterkey(@Nullable String masterkey) { this.masterkey = masterkey; } - public void setAuthenticationPublicKey(String authenticationPublicKey) { + public void setAuthenticationPublicKey(@Nullable String authenticationPublicKey) { this.authenticationPublicKey = authenticationPublicKey; } - public String getAuthenticationPrivateKey() { + public @Nullable String getAuthenticationPrivateKey() { return authenticationPrivateKey; } - public String getAuthenticationPublicKey() { + public @Nullable String getAuthenticationPublicKey() { return authenticationPublicKey; } - public void setAuthenticationPrivateKey(String authenticationPrivateKey) { + public void setAuthenticationPrivateKey(@Nullable String authenticationPrivateKey) { this.authenticationPrivateKey = authenticationPrivateKey; } @@ -223,11 +224,11 @@ public void setCreationTime(Instant creationTime) { this.creationTime = creationTime; } - public String getDescription() { + public @Nullable String getDescription() { return description; } - public void setDescription(String description) { + public void setDescription(@Nullable String description) { this.description = description; } @@ -298,22 +299,21 @@ public String toString() { public static class Repository implements PanacheRepositoryBase { public Stream findAccessibleByUser(String userId) { - return find("#Vault.accessibleByUser", Parameters.with("userId", userId)).stream(); + return find("#Vault.accessibleByUser", Map.of("userId", userId)).stream(); } public Stream findRecoverable(String userId) { - return find("#Vault.recoverableByUser", Parameters.with("councilMemberId", userId)).stream(); + return find("#Vault.recoverableByUser", Map.of("councilMemberId", userId)).stream(); } public Stream findAccessibleByUser(String userId, VaultAccess.Role role) { - return find("#Vault.accessibleByUserAndRole", Parameters.with("userId", userId).and("role", role)).stream(); + return find("#Vault.accessibleByUserAndRole", Map.of("userId", userId, "role", role)).stream(); } public Stream findAllInList(List ids) { - return Batch.of(200).run(ids, Stream.of(), (batch, result) -> { - Stream partialResult = find("#Vault.allInList", Parameters.with("ids", batch)).stream(); - return Stream.concat(result, partialResult); - }); + return ids.stream() + .gather(Gatherers.windowFixed(200)) + .flatMap(batch -> find("#Vault.allInList", Map.of("ids", batch)).stream()); } @Transactional(Transactional.TxType.REQUIRED) diff --git a/backend/src/main/java/org/cryptomator/hub/entities/VaultAccess.java b/backend/src/main/java/org/cryptomator/hub/entities/VaultAccess.java index 64ce7e6e9..ac14b6ab9 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/VaultAccess.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/VaultAccess.java @@ -1,7 +1,6 @@ package org.cryptomator.hub.entities; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; @@ -15,6 +14,7 @@ import jakarta.persistence.NamedQuery; import jakarta.persistence.Table; +import java.util.Map; import java.util.UUID; import java.util.stream.Stream; @@ -127,19 +127,19 @@ public record Id(@Column(name = "vault_id") UUID vaultId, @Column(name = "author public static class Repository implements PanacheRepositoryBase { public Stream forVault(UUID vaultId) { - return find("#VaultAccess.forVault", Parameters.with("vaultId", vaultId)).stream(); + return find("#VaultAccess.forVault", Map.of("vaultId", vaultId)).stream(); } public long countByAuthority(String authorityId) { - return count("#VaultAccess.countByAuthority", Parameters.with("authorityId", authorityId)); + return count("#VaultAccess.countByAuthority", Map.of("authorityId", authorityId)); } public Stream findByAuthority(String authorityId) { - return find("#VaultAccess.findByAuthority", Parameters.with("authorityId", authorityId)).stream(); + return find("#VaultAccess.findByAuthority", Map.of("authorityId", authorityId)).stream(); } public long delete(UUID vaultId, Iterable authorityIds) { - return delete("#VaultAccess.deleteSpecific", Parameters.with("vaultId", vaultId).and("authorityIds", authorityIds)); + return delete("#VaultAccess.deleteSpecific", Map.of("vaultId", vaultId, "authorityIds", authorityIds)); } } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java index 8df4baeb7..da637f0b3 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java @@ -2,7 +2,6 @@ import io.quarkus.hibernate.orm.panache.PanacheQuery; import io.quarkus.hibernate.orm.panache.PanacheRepository; -import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorColumn; @@ -18,6 +17,7 @@ import java.time.Instant; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Stream; @@ -124,11 +124,7 @@ public static class Repository implements PanacheRepository { public Stream findAllInPeriod(Instant startDate, Instant endDate, List type, long paginationId, boolean ascending, int pageSize) { var allTypes = type.isEmpty(); - var parameters = Parameters.with("startDate", startDate) - .and("endDate", endDate) - .and("paginationId", paginationId) - .and("types", type) - .and("allTypes", allTypes); + var parameters = Map.of("startDate", startDate, "endDate", endDate, "paginationId", paginationId, "types", type, "allTypes", allTypes); final PanacheQuery query; if (ascending) { @@ -141,7 +137,7 @@ public Stream findAllInPeriod(Instant startDate, Instant endDate, Li } public Stream findLastVaultKeyRetrieve(Set deviceIds) { - return find("#AuditEvent.lastVaultKeyRetrieve", Parameters.with("deviceIds", deviceIds)).stream(); + return find("#AuditEvent.lastVaultKeyRetrieve", Map.of("deviceIds", deviceIds)).stream(); } } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEvent.java index 6117336a9..21144520d 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEvent.java @@ -6,7 +6,8 @@ import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Table; -import org.cryptomator.hub.entities.Device; +import org.cryptomator.hub.entities.Device.Type; +import org.jspecify.annotations.Nullable; import java.util.Objects; @@ -18,47 +19,47 @@ public class DeviceRegisteredEvent extends AuditEvent { public static final String TYPE = "DEVICE_REGISTER"; @Column(name = "registered_by") - private String registeredBy; + private @Nullable String registeredBy; @Column(name = "device_id") - private String deviceId; + private @Nullable String deviceId; @Column(name = "device_name") - private String deviceName; + private @Nullable String deviceName; @Column(name = "device_type") @Enumerated(EnumType.STRING) - private Device.Type deviceType; + private @Nullable Type deviceType; - public String getRegisteredBy() { + public @Nullable String getRegisteredBy() { return registeredBy; } - public void setRegisteredBy(String registeredBy) { + public void setRegisteredBy(@Nullable String registeredBy) { this.registeredBy = registeredBy; } - public String getDeviceId() { + public @Nullable String getDeviceId() { return deviceId; } - public void setDeviceId(String deviceId) { + public void setDeviceId(@Nullable String deviceId) { this.deviceId = deviceId; } - public String getDeviceName() { + public @Nullable String getDeviceName() { return deviceName; } - public void setDeviceName(String deviceName) { + public void setDeviceName(@Nullable String deviceName) { this.deviceName = deviceName; } - public Device.Type getDeviceType() { + public @Nullable Type getDeviceType() { return deviceType; } - public void setDeviceType(Device.Type deviceType) { + public void setDeviceType(@Nullable Type deviceType) { this.deviceType = deviceType; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEvent.java index 71ef0bc0c..7358ef7fd 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEvent.java @@ -4,6 +4,7 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; @@ -15,24 +16,24 @@ public class DeviceRemovedEvent extends AuditEvent { public static final String TYPE = "DEVICE_REMOVE"; @Column(name = "removed_by") - private String removedBy; + private @Nullable String removedBy; @Column(name = "device_id") - private String deviceId; + private @Nullable String deviceId; - public String getRemovedBy() { + public @Nullable String getRemovedBy() { return removedBy; } - public void setRemovedBy(String removedBy) { + public void setRemovedBy(@Nullable String removedBy) { this.removedBy = removedBy; } - public String getDeviceId() { + public @Nullable String getDeviceId() { return deviceId; } - public void setDeviceId(String deviceId) { + public void setDeviceId(@Nullable String deviceId) { this.deviceId = deviceId; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/EmergencyAccessRecoveryAbortedEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/EmergencyAccessRecoveryAbortedEvent.java index 87be806fb..8231d5db5 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/EmergencyAccessRecoveryAbortedEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/EmergencyAccessRecoveryAbortedEvent.java @@ -4,6 +4,7 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; import java.util.UUID; @@ -25,7 +26,7 @@ public class EmergencyAccessRecoveryAbortedEvent extends AuditEvent { private String councilMemberId; @Column(name = "ip_address") - private String ipAddress; + private @Nullable String ipAddress; public UUID getVaultId() { return vaultId; @@ -51,11 +52,11 @@ public void setCouncilMemberId(String councilMemberId) { this.councilMemberId = councilMemberId; } - public String getIpAddress() { + public @Nullable String getIpAddress() { return ipAddress; } - public void setIpAddress(String ipAddress) { + public void setIpAddress(@Nullable String ipAddress) { this.ipAddress = ipAddress; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/EmergencyAccessRecoveryCompletedEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/EmergencyAccessRecoveryCompletedEvent.java index 0c5ec4c7c..7b042099f 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/EmergencyAccessRecoveryCompletedEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/EmergencyAccessRecoveryCompletedEvent.java @@ -4,6 +4,7 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; import java.util.UUID; @@ -22,7 +23,7 @@ public class EmergencyAccessRecoveryCompletedEvent extends AuditEvent { private String councilMemberId; @Column(name = "ip_address") - private String ipAddress; + private @Nullable String ipAddress; public UUID getProcessId() { return processId; @@ -40,11 +41,11 @@ public void setCouncilMemberId(String councilMemberId) { this.councilMemberId = councilMemberId; } - public String getIpAddress() { + public @Nullable String getIpAddress() { return ipAddress; } - public void setIpAddress(String ipAddress) { + public void setIpAddress(@Nullable String ipAddress) { this.ipAddress = ipAddress; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/EventLogger.java b/backend/src/main/java/org/cryptomator/hub/entities/events/EventLogger.java index 15ea124e3..8e94b75c1 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/EventLogger.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/EventLogger.java @@ -11,8 +11,12 @@ @ApplicationScoped public class EventLogger { + private final AuditEvent.Repository auditEventRepository; + @Inject - AuditEvent.Repository auditEventRepository; + EventLogger(AuditEvent.Repository auditEventRepository) { + this.auditEventRepository = auditEventRepository; + } public void logVaultCreated(String createdBy, UUID vaultId, String vaultName, String vaultDescription) { var event = new VaultCreatedEvent(); diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/SettingWotUpdateEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/SettingWotUpdateEvent.java index dd336c190..a30e6967a 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/SettingWotUpdateEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/SettingWotUpdateEvent.java @@ -4,6 +4,7 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; @@ -15,7 +16,7 @@ public class SettingWotUpdateEvent extends AuditEvent { public static final String TYPE = "SETTING_WOT_UPDATE"; @Column(name = "updated_by") - private String updatedBy; + private @Nullable String updatedBy; @Column(name = "wot_max_depth") private int wotMaxDepth; @@ -23,11 +24,11 @@ public class SettingWotUpdateEvent extends AuditEvent { @Column(name = "wot_id_verify_len") private int wotIdVerifyLen; - public String getUpdatedBy() { + public @Nullable String getUpdatedBy() { return updatedBy; } - public void setUpdatedBy(String updatedBy) { + public void setUpdatedBy(@Nullable String updatedBy) { this.updatedBy = updatedBy; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/SignedWotIdEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/SignedWotIdEvent.java index 4566619f9..e2ed7e2c4 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/SignedWotIdEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/SignedWotIdEvent.java @@ -4,6 +4,7 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; @@ -15,46 +16,46 @@ public class SignedWotIdEvent extends AuditEvent { public static final String TYPE = "SIGN_WOT_ID"; @Column(name = "user_id") - private String userId; + private @Nullable String userId; @Column(name = "signer_id") - private String signerId; + private @Nullable String signerId; @Column(name = "signer_key") - private String signerKey; + private @Nullable String signerKey; @Column(name = "signature") - private String signature; + private @Nullable String signature; - public String getUserId() { + public @Nullable String getUserId() { return userId; } - public void setUserId(String userId) { + public void setUserId(@Nullable String userId) { this.userId = userId; } - public String getSignerId() { + public @Nullable String getSignerId() { return signerId; } - public void setSignerId(String signerId) { + public void setSignerId(@Nullable String signerId) { this.signerId = signerId; } - public String getSignerKey() { + public @Nullable String getSignerKey() { return signerKey; } - public void setSignerKey(String signerKey) { + public void setSignerKey(@Nullable String signerKey) { this.signerKey = signerKey; } - public String getSignature() { + public @Nullable String getSignature() { return signature; } - public void setSignature(String signature) { + public void setSignature(@Nullable String signature) { this.signature = signature; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/UserAccountResetEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/UserAccountResetEvent.java index 47e733f0d..f9f740580 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/UserAccountResetEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/UserAccountResetEvent.java @@ -4,6 +4,7 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; @@ -15,13 +16,13 @@ public class UserAccountResetEvent extends AuditEvent { public static final String TYPE = "USER_ACCOUNT_RESET"; @Column(name = "reset_by") - private String resetBy; + private @Nullable String resetBy; - public String getResetBy() { + public @Nullable String getResetBy() { return resetBy; } - public void setResetBy(String resetBy) { + public void setResetBy(@Nullable String resetBy) { this.resetBy = resetBy; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/UserKeysChangeEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/UserKeysChangeEvent.java index 24ad18f3f..09014ec90 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/UserKeysChangeEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/UserKeysChangeEvent.java @@ -4,6 +4,7 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; @@ -15,24 +16,24 @@ public class UserKeysChangeEvent extends AuditEvent { public static final String TYPE = "USER_KEYS_CHANGE"; @Column(name = "changed_by") - private String changedBy; + private @Nullable String changedBy; @Column(name = "user_name") - private String userName; + private @Nullable String userName; - public String getChangedBy() { + public @Nullable String getChangedBy() { return changedBy; } - public void setChangedBy(String changedBy) { + public void setChangedBy(@Nullable String changedBy) { this.changedBy = changedBy; } - public String getUserName() { + public @Nullable String getUserName() { return userName; } - public void setUserName(String userName) { + public void setUserName(@Nullable String userName) { this.userName = userName; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/UserSetupCodeChangeEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/UserSetupCodeChangeEvent.java index b846bb5ff..a1059fa5e 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/UserSetupCodeChangeEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/UserSetupCodeChangeEvent.java @@ -4,6 +4,7 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; @@ -15,13 +16,13 @@ public class UserSetupCodeChangeEvent extends AuditEvent { public static final String TYPE = "USER_SETUP_CODE_CHANGE"; @Column(name = "changed_by") - private String changedBy; + private @Nullable String changedBy; - public String getChangedBy() { + public @Nullable String getChangedBy() { return changedBy; } - public void setChangedBy(String changedBy) { + public void setChangedBy(@Nullable String changedBy) { this.changedBy = changedBy; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultAccessGrantedEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultAccessGrantedEvent.java index 4314e0025..4e4521548 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultAccessGrantedEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultAccessGrantedEvent.java @@ -4,6 +4,7 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; import java.util.UUID; @@ -16,35 +17,35 @@ public class VaultAccessGrantedEvent extends AuditEvent { public static final String TYPE = "VAULT_ACCESS_GRANT"; @Column(name = "granted_by") - private String grantedBy; + private @Nullable String grantedBy; @Column(name = "vault_id") - private UUID vaultId; + private @Nullable UUID vaultId; @Column(name = "authority_id") - private String authorityId; + private @Nullable String authorityId; - public String getGrantedBy() { + public @Nullable String getGrantedBy() { return grantedBy; } - public void setGrantedBy(String grantedBy) { + public void setGrantedBy(@Nullable String grantedBy) { this.grantedBy = grantedBy; } - public UUID getVaultId() { + public @Nullable UUID getVaultId() { return vaultId; } - public void setVaultId(UUID vaultId) { + public void setVaultId(@Nullable UUID vaultId) { this.vaultId = vaultId; } - public String getAuthorityId() { + public @Nullable String getAuthorityId() { return authorityId; } - public void setAuthorityId(String authorityId) { + public void setAuthorityId(@Nullable String authorityId) { this.authorityId = authorityId; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEvent.java index 8a1e9863b..06924109f 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEvent.java @@ -4,6 +4,7 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; import java.util.UUID; @@ -16,46 +17,46 @@ public class VaultCreatedEvent extends AuditEvent { public static final String TYPE = "VAULT_CREATE"; @Column(name = "created_by") - private String createdBy; + private @Nullable String createdBy; @Column(name = "vault_id") - private UUID vaultId; + private @Nullable UUID vaultId; @Column(name = "vault_name") - private String vaultName; + private @Nullable String vaultName; @Column(name = "vault_description") - private String vaultDescription; + private @Nullable String vaultDescription; - public String getCreatedBy() { + public @Nullable String getCreatedBy() { return createdBy; } - public void setCreatedBy(String createdBy) { + public void setCreatedBy(@Nullable String createdBy) { this.createdBy = createdBy; } - public UUID getVaultId() { + public @Nullable UUID getVaultId() { return vaultId; } - public void setVaultId(UUID vaultId) { + public void setVaultId(@Nullable UUID vaultId) { this.vaultId = vaultId; } - public String getVaultName() { + public @Nullable String getVaultName() { return vaultName; } - public void setVaultName(String vaultName) { + public void setVaultName(@Nullable String vaultName) { this.vaultName = vaultName; } - public String getVaultDescription() { + public @Nullable String getVaultDescription() { return vaultDescription; } - public void setVaultDescription(String vaultDescription) { + public void setVaultDescription(@Nullable String vaultDescription) { this.vaultDescription = vaultDescription; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEvent.java index 81f845a3b..ade3ce37c 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEvent.java @@ -6,6 +6,7 @@ import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; import java.util.UUID; @@ -18,58 +19,58 @@ public class VaultKeyRetrievedEvent extends AuditEvent { public static final String TYPE = "VAULT_KEY_RETRIEVE"; @Column(name = "retrieved_by") - private String retrievedBy; + private @Nullable String retrievedBy; @Column(name = "vault_id") - private UUID vaultId; + private @Nullable UUID vaultId; @Column(name = "result") @Enumerated(EnumType.STRING) - private Result result; + private @Nullable Result result; @Column(name = "ip_address") - private String ipAddress; + private @Nullable String ipAddress; @Column(name = "device_id") - private String deviceId; + private @Nullable String deviceId; - public String getRetrievedBy() { + public @Nullable String getRetrievedBy() { return retrievedBy; } - public void setRetrievedBy(String retrievedBy) { + public void setRetrievedBy(@Nullable String retrievedBy) { this.retrievedBy = retrievedBy; } - public UUID getVaultId() { + public @Nullable UUID getVaultId() { return vaultId; } - public void setVaultId(UUID vaultId) { + public void setVaultId(@Nullable UUID vaultId) { this.vaultId = vaultId; } - public Result getResult() { + public @Nullable Result getResult() { return result; } - public void setResult(Result result) { + public void setResult(@Nullable Result result) { this.result = result; } - public String getIpAddress() { + public @Nullable String getIpAddress() { return ipAddress; } - public void setIpAddress(String ipAddress) { + public void setIpAddress(@Nullable String ipAddress) { this.ipAddress = ipAddress; } - public String getDeviceId() { + public @Nullable String getDeviceId() { return deviceId; } - public void setDeviceId(String device) { + public void setDeviceId(@Nullable String device) { this.deviceId = device; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEvent.java index 0472f5593..43e411919 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEvent.java @@ -7,6 +7,7 @@ import jakarta.persistence.Enumerated; import jakarta.persistence.Table; import org.cryptomator.hub.entities.VaultAccess; +import org.jspecify.annotations.Nullable; import java.util.Objects; import java.util.UUID; @@ -19,39 +20,39 @@ public class VaultMemberAddedEvent extends AuditEvent { public static final String TYPE = "VAULT_MEMBER_ADD"; @Column(name = "added_by") - private String addedBy; + private @Nullable String addedBy; @Column(name = "vault_id") - private UUID vaultId; + private @Nullable UUID vaultId; @Column(name = "authority_id") - private String authorityId; + private @Nullable String authorityId; @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) private VaultAccess.Role role; - public String getAddedBy() { + public @Nullable String getAddedBy() { return addedBy; } - public void setAddedBy(String addedBy) { + public void setAddedBy(@Nullable String addedBy) { this.addedBy = addedBy; } - public UUID getVaultId() { + public @Nullable UUID getVaultId() { return vaultId; } - public void setVaultId(UUID vaultId) { + public void setVaultId(@Nullable UUID vaultId) { this.vaultId = vaultId; } - public String getAuthorityId() { + public @Nullable String getAuthorityId() { return authorityId; } - public void setAuthorityId(String authorityId) { + public void setAuthorityId(@Nullable String authorityId) { this.authorityId = authorityId; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEvent.java index de236c9c5..e7a823204 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEvent.java @@ -4,6 +4,7 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; import java.util.UUID; @@ -16,35 +17,35 @@ public class VaultMemberRemovedEvent extends AuditEvent { public static final String TYPE = "VAULT_MEMBER_REMOVE"; @Column(name = "removed_by") - private String removedBy; + private @Nullable String removedBy; @Column(name = "vault_id") - private UUID vaultId; + private @Nullable UUID vaultId; @Column(name = "authority_id") - private String authorityId; + private @Nullable String authorityId; - public String getRemovedBy() { + public @Nullable String getRemovedBy() { return removedBy; } - public void setRemovedBy(String removedBy) { + public void setRemovedBy(@Nullable String removedBy) { this.removedBy = removedBy; } - public UUID getVaultId() { + public @Nullable UUID getVaultId() { return vaultId; } - public void setVaultId(UUID vaultId) { + public void setVaultId(@Nullable UUID vaultId) { this.vaultId = vaultId; } - public String getAuthorityId() { + public @Nullable String getAuthorityId() { return authorityId; } - public void setAuthorityId(String authorityId) { + public void setAuthorityId(@Nullable String authorityId) { this.authorityId = authorityId; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEvent.java index c5cf8c479..0337a4af6 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEvent.java @@ -7,6 +7,7 @@ import jakarta.persistence.Enumerated; import jakarta.persistence.Table; import org.cryptomator.hub.entities.VaultAccess; +import org.jspecify.annotations.Nullable; import java.util.Objects; import java.util.UUID; @@ -19,39 +20,39 @@ public class VaultMemberUpdatedEvent extends AuditEvent { public static final String TYPE = "VAULT_MEMBER_UPDATE"; @Column(name = "updated_by") - private String updatedBy; + private @Nullable String updatedBy; @Column(name = "vault_id") - private UUID vaultId; + private @Nullable UUID vaultId; @Column(name = "authority_id") - private String authorityId; + private @Nullable String authorityId; @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) private VaultAccess.Role role; - public String getUpdatedBy() { + public @Nullable String getUpdatedBy() { return updatedBy; } - public void setUpdatedBy(String updatedBy) { + public void setUpdatedBy(@Nullable String updatedBy) { this.updatedBy = updatedBy; } - public UUID getVaultId() { + public @Nullable UUID getVaultId() { return vaultId; } - public void setVaultId(UUID vaultId) { + public void setVaultId(@Nullable UUID vaultId) { this.vaultId = vaultId; } - public String getAuthorityId() { + public @Nullable String getAuthorityId() { return authorityId; } - public void setAuthorityId(String authorityId) { + public void setAuthorityId(@Nullable String authorityId) { this.authorityId = authorityId; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEvent.java index a1344b312..b62953604 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEvent.java @@ -4,6 +4,7 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; import java.util.UUID; @@ -16,24 +17,24 @@ public class VaultOwnershipClaimedEvent extends AuditEvent { public static final String TYPE = "VAULT_OWNERSHIP_CLAIM"; @Column(name = "claimed_by") - private String claimedBy; + private @Nullable String claimedBy; @Column(name = "vault_id") - private UUID vaultId; + private @Nullable UUID vaultId; - public String getClaimedBy() { + public @Nullable String getClaimedBy() { return claimedBy; } - public void setClaimedBy(String claimedBy) { + public void setClaimedBy(@Nullable String claimedBy) { this.claimedBy = claimedBy; } - public UUID getVaultId() { + public @Nullable UUID getVaultId() { return vaultId; } - public void setVaultId(UUID vaultId) { + public void setVaultId(@Nullable UUID vaultId) { this.vaultId = vaultId; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEvent.java index d6ea6c382..081ead968 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEvent.java @@ -4,6 +4,7 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import org.jspecify.annotations.Nullable; import java.util.Objects; import java.util.UUID; @@ -16,49 +17,49 @@ public class VaultUpdatedEvent extends AuditEvent { public static final String TYPE = "VAULT_UPDATE"; @Column(name = "updated_by") - private String updatedBy; + private @Nullable String updatedBy; @Column(name = "vault_id") - private UUID vaultId; + private @Nullable UUID vaultId; @Column(name = "vault_name") - private String vaultName; + private @Nullable String vaultName; @Column(name = "vault_description") - private String vaultDescription; + private @Nullable String vaultDescription; @Column(name = "vault_archived") private boolean vaultArchived; - public String getUpdatedBy() { + public @Nullable String getUpdatedBy() { return updatedBy; } - public void setUpdatedBy(String updatedBy) { + public void setUpdatedBy(@Nullable String updatedBy) { this.updatedBy = updatedBy; } - public UUID getVaultId() { + public @Nullable UUID getVaultId() { return vaultId; } - public void setVaultId(UUID vaultId) { + public void setVaultId(@Nullable UUID vaultId) { this.vaultId = vaultId; } - public String getVaultName() { + public @Nullable String getVaultName() { return vaultName; } - public void setVaultName(String vaultName) { + public void setVaultName(@Nullable String vaultName) { this.vaultName = vaultName; } - public String getVaultDescription() { + public @Nullable String getVaultDescription() { return vaultDescription; } - public void setVaultDescription(String vaultDescription) { + public void setVaultDescription(@Nullable String vaultDescription) { this.vaultDescription = vaultDescription; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/package-info.java b/backend/src/main/java/org/cryptomator/hub/entities/events/package-info.java new file mode 100644 index 000000000..1f9717d17 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.cryptomator.hub.entities.events; + +import org.jspecify.annotations.NullMarked; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/package-info.java b/backend/src/main/java/org/cryptomator/hub/entities/package-info.java new file mode 100644 index 000000000..107d134c5 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.cryptomator.hub.entities; + +import org.jspecify.annotations.NullMarked; diff --git a/backend/src/main/java/org/cryptomator/hub/filters/ActiveLicenseFilter.java b/backend/src/main/java/org/cryptomator/hub/filters/ActiveLicenseFilter.java index e695899bc..e3a491660 100644 --- a/backend/src/main/java/org/cryptomator/hub/filters/ActiveLicenseFilter.java +++ b/backend/src/main/java/org/cryptomator/hub/filters/ActiveLicenseFilter.java @@ -16,8 +16,12 @@ @ActiveLicense public class ActiveLicenseFilter implements ContainerRequestFilter { + private final LicenseHolder license; + @Inject - LicenseHolder license; + ActiveLicenseFilter(LicenseHolder license) { + this.license = license; + } @Override public void filter(ContainerRequestContext requestContext) { diff --git a/backend/src/main/java/org/cryptomator/hub/filters/FrontendRootPathFilter.java b/backend/src/main/java/org/cryptomator/hub/filters/FrontendRootPathFilter.java index bd126bd5b..ca70f5fd9 100644 --- a/backend/src/main/java/org/cryptomator/hub/filters/FrontendRootPathFilter.java +++ b/backend/src/main/java/org/cryptomator/hub/filters/FrontendRootPathFilter.java @@ -22,9 +22,12 @@ */ public class FrontendRootPathFilter extends HttpFilter { + private final Provider publicRootPath; + @Inject - @ConfigProperty(name = "hub.public-root-path", defaultValue = "") - Provider publicRootPath; + FrontendRootPathFilter(@ConfigProperty(name = "hub.public-root-path", defaultValue = "") Provider publicRootPath) { + this.publicRootPath = publicRootPath; + } @Override protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { diff --git a/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java b/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java index b542f63b6..498e4fff5 100644 --- a/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java +++ b/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java @@ -30,21 +30,22 @@ @VaultRole public class VaultRoleFilter implements ContainerRequestFilter { - @Inject - JsonWebToken jwt; - - @Inject - EffectiveVaultAccess.Repository effectiveVaultAccessRepo; - - @Inject - EmergencyRecoveryProcess.Repository recoveryRepo; - - @Inject - Vault.Repository vaultRepo; + private final JsonWebToken jwt; + private final EffectiveVaultAccess.Repository effectiveVaultAccessRepo; + private final EmergencyRecoveryProcess.Repository recoveryRepo; + private final Vault.Repository vaultRepo; @Context ResourceInfo resourceInfo; + @Inject + VaultRoleFilter(JsonWebToken jwt, EffectiveVaultAccess.Repository effectiveVaultAccessRepo, EmergencyRecoveryProcess.Repository recoveryRepo, Vault.Repository vaultRepo) { + this.jwt = jwt; + this.effectiveVaultAccessRepo = effectiveVaultAccessRepo; + this.recoveryRepo = recoveryRepo; + this.vaultRepo = vaultRepo; + } + @Override public void filter(ContainerRequestContext requestContext) throws NotFoundException, ForbiddenException, NotAuthorizedException { var annotation = resourceInfo.getResourceMethod().getAnnotation(VaultRole.class); diff --git a/backend/src/main/java/org/cryptomator/hub/filters/package-info.java b/backend/src/main/java/org/cryptomator/hub/filters/package-info.java new file mode 100644 index 000000000..f50dbb854 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/filters/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.cryptomator.hub.filters; + +import org.jspecify.annotations.NullMarked; diff --git a/backend/src/main/java/org/cryptomator/hub/flyway/package-info.java b/backend/src/main/java/org/cryptomator/hub/flyway/package-info.java new file mode 100644 index 000000000..68c646958 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/flyway/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.cryptomator.hub.flyway; + +import org.jspecify.annotations.NullMarked; diff --git a/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityProvider.java b/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityProvider.java index b22f55dd3..226cf3cef 100644 --- a/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityProvider.java +++ b/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityProvider.java @@ -26,11 +26,14 @@ public class KeycloakAuthorityProvider { //visible for testing static final int MAX_COUNT_PER_REQUEST = 5_000; - @Inject - Keycloak keycloak; + private final Keycloak keycloak; + private final String keycloakRealm; - @ConfigProperty(name = "hub.keycloak.realm") - String keycloakRealm; + @Inject + KeycloakAuthorityProvider(Keycloak keycloak, @ConfigProperty(name = "hub.keycloak.realm") String keycloakRealm) { + this.keycloak = keycloak; + this.keycloakRealm = keycloakRealm; + } public List users() { return users(keycloak.realm(keycloakRealm)); diff --git a/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPuller.java b/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPuller.java index 95d0e902d..bf84a21a3 100644 --- a/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPuller.java +++ b/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPuller.java @@ -46,26 +46,25 @@ public class KeycloakAuthorityPuller { private static final Logger LOG = Logger.getLogger(KeycloakAuthorityPuller.class); - @Inject - Keycloak keycloak; - @Inject - User.Repository userRepo; - @Inject - Group.Repository groupRepo; - @Inject - KeycloakAuthorityProvider remoteUserProvider; - @Inject - EffectiveGroupMembership.Repository effectiveGroupMembershipRepo; - @Inject - KeycloakRealmRoles realmRoles; - - @ConfigProperty(name = "hub.keycloak.realm") - String keycloakRealm; - + private final Keycloak keycloak; + private final User.Repository userRepo; + private final Group.Repository groupRepo; + private final KeycloakAuthorityProvider remoteUserProvider; + private final EffectiveGroupMembership.Repository effectiveGroupMembershipRepo; + private final KeycloakRealmRoles realmRoles; + + // visible for testing RealmResource realm; - @PostConstruct - public void setup() { + @Inject + KeycloakAuthorityPuller(Keycloak keycloak, User.Repository userRepo, Group.Repository groupRepo, KeycloakAuthorityProvider remoteUserProvider, EffectiveGroupMembership.Repository effectiveGroupMembershipRepo, KeycloakRealmRoles realmRoles, @ConfigProperty(name = "hub.keycloak.realm") String keycloakRealm) { + this.keycloak = keycloak; + this.userRepo = userRepo; + this.groupRepo = groupRepo; + this.remoteUserProvider = remoteUserProvider; + this.effectiveGroupMembershipRepo = effectiveGroupMembershipRepo; + this.realmRoles = realmRoles; + this.realm = keycloak.realm(keycloakRealm); } @@ -397,7 +396,7 @@ public Group syncGroup(GroupResource groupResource, GroupRepresentation keycloak } var pictureUrl = KeycloakAuthorityProvider.parsePictureUrl(keycloakGroup.getAttributes()); - var dbMembers = userRepo.findByIds(memberIds).collect(Collectors.toMap(User::getId, Function.identity())); + var dbMembers = userRepo.streamByIds(memberIds).collect(Collectors.toMap(User::getId, Function.identity())); applyGroup(dbGroup, keycloakGroup.getName(), pictureUrl, new HashSet<>(memberIds), dbMembers::get); groupRepo.persist(dbGroup); diff --git a/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakGroupDto.java b/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakGroupDto.java index 3a849a890..25214cc9f 100644 --- a/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakGroupDto.java +++ b/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakGroupDto.java @@ -1,6 +1,8 @@ package org.cryptomator.hub.keycloak; +import org.jspecify.annotations.Nullable; + import java.util.Set; -public record KeycloakGroupDto(String id, String name, String pictureUrl, Set members) { +public record KeycloakGroupDto(String id, String name, @Nullable String pictureUrl, Set members) { } diff --git a/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakRealmRoles.java b/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakRealmRoles.java index 259a3fc06..51f3545ac 100644 --- a/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakRealmRoles.java +++ b/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakRealmRoles.java @@ -11,14 +11,16 @@ @ApplicationScoped public class KeycloakRealmRoles { - @Inject - Keycloak keycloak; - - @ConfigProperty(name = "hub.keycloak.realm") - String keycloakRealm; - + private final Keycloak keycloak; + private final String keycloakRealm; private final EnumMap cachedRoles = new EnumMap<>(RealmRole.class); + @Inject + KeycloakRealmRoles(Keycloak keycloak, @ConfigProperty(name = "hub.keycloak.realm") String keycloakRealm) { + this.keycloak = keycloak; + this.keycloakRealm = keycloakRealm; + } + public RoleRepresentation getRealmRole(RealmRole realmRole) { return cachedRoles.computeIfAbsent(realmRole, this::fetchRealmRole); } diff --git a/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakUserDto.java b/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakUserDto.java index d6417d96c..e79319ec1 100644 --- a/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakUserDto.java +++ b/backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakUserDto.java @@ -1,4 +1,6 @@ package org.cryptomator.hub.keycloak; -public record KeycloakUserDto(String id, String name, String email, String firstName, String lastName, String pictureUrl, boolean enabled) { +import org.jspecify.annotations.Nullable; + +public record KeycloakUserDto(String id, String name, @Nullable String email, @Nullable String firstName, @Nullable String lastName, @Nullable String pictureUrl, boolean enabled) { } diff --git a/backend/src/main/java/org/cryptomator/hub/keycloak/package-info.java b/backend/src/main/java/org/cryptomator/hub/keycloak/package-info.java new file mode 100644 index 000000000..bd876b0a6 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/keycloak/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.cryptomator.hub.keycloak; + +import org.jspecify.annotations.NullMarked; diff --git a/backend/src/main/java/org/cryptomator/hub/license/HubLicenseEntitlements.java b/backend/src/main/java/org/cryptomator/hub/license/HubLicenseEntitlements.java index 5a4f88b64..c1ae72147 100644 --- a/backend/src/main/java/org/cryptomator/hub/license/HubLicenseEntitlements.java +++ b/backend/src/main/java/org/cryptomator/hub/license/HubLicenseEntitlements.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import io.quarkus.runtime.annotations.RegisterForReflection; +import org.jspecify.annotations.Nullable; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -14,9 +15,9 @@ public record HubLicenseEntitlements(@JsonProperty("seats") long seats, @JsonProperty("auditLogRetentionDays") long auditLogRetentionDays, @JsonProperty("emergencyAccessEnabled") boolean emergencyAccessEnabled, @JsonProperty("keycloakAccessEnabled") boolean keycloakAccessEnabled, - @JsonProperty("iosLicense") String iosLicense, - @JsonProperty("androidLicense") String androidLicense, - @JsonProperty("desktopLicense") String desktopLicense) { + @JsonProperty("iosLicense") @Nullable String iosLicense, + @JsonProperty("androidLicense") @Nullable String androidLicense, + @JsonProperty("desktopLicense") @Nullable String desktopLicense) { /** * Calculates the earliest point of time for audit log entries to still be retained. @@ -26,7 +27,7 @@ public record HubLicenseEntitlements(@JsonProperty("seats") long seats, public Instant auditLogRetentionThreshold() { try { return Instant.now().minus(auditLogRetentionDays(), ChronoUnit.DAYS).truncatedTo(ChronoUnit.DAYS); - } catch (ArithmeticException e) { + } catch (ArithmeticException _) { return Instant.MIN; } } @@ -57,15 +58,15 @@ public HubLicenseEntitlements withKeycloakAccessEnabled(boolean keycloakAccessEn return new HubLicenseEntitlements(this.seats, this.showTrialHint, this.auditLogRetentionDays, this.emergencyAccessEnabled, keycloakAccessEnabled, this.iosLicense, this.androidLicense, this.desktopLicense); } - public HubLicenseEntitlements withIosLicense(String iosLicense) { + public HubLicenseEntitlements withIosLicense(@Nullable String iosLicense) { return new HubLicenseEntitlements(this.seats, this.showTrialHint, this.auditLogRetentionDays, this.emergencyAccessEnabled, this.keycloakAccessEnabled, iosLicense, this.androidLicense, this.desktopLicense); } - public HubLicenseEntitlements withAndroidLicense(String androidLicense) { + public HubLicenseEntitlements withAndroidLicense(@Nullable String androidLicense) { return new HubLicenseEntitlements(this.seats, this.showTrialHint, this.auditLogRetentionDays, this.emergencyAccessEnabled, this.keycloakAccessEnabled, this.iosLicense, androidLicense, this.desktopLicense); } - public HubLicenseEntitlements withDesktopLicense(String desktopLicense) { + public HubLicenseEntitlements withDesktopLicense(@Nullable String desktopLicense) { return new HubLicenseEntitlements(this.seats, this.showTrialHint, this.auditLogRetentionDays, this.emergencyAccessEnabled, this.keycloakAccessEnabled, this.iosLicense, this.androidLicense, desktopLicense); } diff --git a/backend/src/main/java/org/cryptomator/hub/license/LicenseHolder.java b/backend/src/main/java/org/cryptomator/hub/license/LicenseHolder.java index 3b1142c3e..e585ad43c 100644 --- a/backend/src/main/java/org/cryptomator/hub/license/LicenseHolder.java +++ b/backend/src/main/java/org/cryptomator/hub/license/LicenseHolder.java @@ -39,39 +39,38 @@ public class LicenseHolder { private static final Logger LOG = Logger.getLogger(LicenseHolder.class); - @Inject - @ConfigProperty(name = "hub.managed-instance", defaultValue = "false") - Boolean managedInstance; - - @Inject - @ConfigProperty(name = "hub.initial-id") - Optional initialId; - - @Inject - @ConfigProperty(name = "hub.initial-license") - Optional initialLicenseToken; - - @Inject - @ConfigProperty(name = "hub.managed-api-username") - Optional managedApiUsername; - - @Inject - @ConfigProperty(name = "hub.managed-api-password") - Optional managedApiPassword; + private final Boolean managedInstance; + private final Optional initialId; + private final Optional initialLicenseToken; + private final Optional managedApiUsername; + private final Optional managedApiPassword; + private final LicenseValidator licenseValidator; + private final RandomSleeper randomSleeper; + private final Settings.Repository settingsRepo; + private final LicenseApi licenseApi; - @Inject - LicenseValidator licenseValidator; - - @Inject - RandomSleeper randomSleeper; + private DecodedJWT license; @Inject - Settings.Repository settingsRepo; - - @RestClient - LicenseApi licenseApi; - - private DecodedJWT license; + LicenseHolder(@ConfigProperty(name = "hub.managed-instance", defaultValue = "false") Boolean managedInstance, + @ConfigProperty(name = "hub.initial-id") Optional initialId, + @ConfigProperty(name = "hub.initial-license") Optional initialLicenseToken, + @ConfigProperty(name = "hub.managed-api-username") Optional managedApiUsername, + @ConfigProperty(name = "hub.managed-api-password") Optional managedApiPassword, + LicenseValidator licenseValidator, + RandomSleeper randomSleeper, + Settings.Repository settingsRepo, + @RestClient LicenseApi licenseApi) { + this.managedInstance = managedInstance; + this.initialId = initialId; + this.initialLicenseToken = initialLicenseToken; + this.managedApiUsername = managedApiUsername; + this.managedApiPassword = managedApiPassword; + this.licenseValidator = licenseValidator; + this.randomSleeper = randomSleeper; + this.settingsRepo = settingsRepo; + this.licenseApi = licenseApi; + } @PostConstruct void init() { @@ -227,7 +226,7 @@ public void refreshLicense() throws IOException { } catch (LicenseRefreshFailedException e) { LOG.errorv("Failed to refresh license token. Request to {0} was answered with response code {1,number,integer}", refreshUrl, e.statusCode); throw new IOException("Failed to refresh license token.", e); - } catch (InterruptedException e) { + } catch (InterruptedException _) { Thread.currentThread().interrupt(); throw new InterruptedIOException("License refresh was interrupted"); } diff --git a/backend/src/main/java/org/cryptomator/hub/license/LicenseValidator.java b/backend/src/main/java/org/cryptomator/hub/license/LicenseValidator.java index f78e862e9..e4a5e1c03 100644 --- a/backend/src/main/java/org/cryptomator/hub/license/LicenseValidator.java +++ b/backend/src/main/java/org/cryptomator/hub/license/LicenseValidator.java @@ -13,9 +13,12 @@ public class LicenseValidator { private static final String[] REQUIRED_CLAIMS = {"jti", "sub", "iat", "exp", "seats"}; // TODO: eventually phase out "seats" claim in favor of "org.cryptomator.hub.entitlements"."seats", see https://github.com/cryptomator/hub/issues/391 + private final JWTVerifier verifier; + @Inject - @Named("licenseVerifier") - JWTVerifier verifier; + LicenseValidator(@Named("licenseVerifier") JWTVerifier verifier) { + this.verifier = verifier; + } /** * Validates the token signature and whether it matches the Hub ID. It does NOT check the expiration date, though. diff --git a/backend/src/main/java/org/cryptomator/hub/license/LicenseVerifierProducer.java b/backend/src/main/java/org/cryptomator/hub/license/LicenseVerifierProducer.java index cb8a22720..3d3073334 100644 --- a/backend/src/main/java/org/cryptomator/hub/license/LicenseVerifierProducer.java +++ b/backend/src/main/java/org/cryptomator/hub/license/LicenseVerifierProducer.java @@ -6,6 +6,7 @@ import com.auth0.jwt.interfaces.JWTVerifier; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; import jakarta.inject.Named; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -38,8 +39,12 @@ public class LicenseVerifierProducer { -----END CERTIFICATE----- """; - @ConfigProperty(name = "hub.license.chain.required-cn") - String licenseChainRequiredCn; + private final String licenseChainRequiredCn; + + @Inject + LicenseVerifierProducer(@ConfigProperty(name = "hub.license.chain.required-cn") String licenseChainRequiredCn) { + this.licenseChainRequiredCn = licenseChainRequiredCn; + } @Produces @ApplicationScoped diff --git a/backend/src/main/java/org/cryptomator/hub/license/package-info.java b/backend/src/main/java/org/cryptomator/hub/license/package-info.java new file mode 100644 index 000000000..6de6a13b4 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/license/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.cryptomator.hub.license; + +import org.jspecify.annotations.NullMarked; diff --git a/backend/src/main/java/org/cryptomator/hub/metrics/package-info.java b/backend/src/main/java/org/cryptomator/hub/metrics/package-info.java new file mode 100644 index 000000000..98dc49fff --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/metrics/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.cryptomator.hub.metrics; + +import org.jspecify.annotations.NullMarked; diff --git a/backend/src/main/java/org/cryptomator/hub/package-info.java b/backend/src/main/java/org/cryptomator/hub/package-info.java new file mode 100644 index 000000000..f177acfb7 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.cryptomator.hub; + +import org.jspecify.annotations.NullMarked; diff --git a/backend/src/main/java/org/cryptomator/hub/util/package-info.java b/backend/src/main/java/org/cryptomator/hub/util/package-info.java new file mode 100644 index 000000000..8baa1fc30 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/util/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.cryptomator.hub.util; + +import org.jspecify.annotations.NullMarked; diff --git a/backend/src/main/java/org/cryptomator/hub/validation/package-info.java b/backend/src/main/java/org/cryptomator/hub/validation/package-info.java new file mode 100644 index 000000000..6ea25d33b --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/validation/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.cryptomator.hub.validation; + +import org.jspecify.annotations.NullMarked; diff --git a/backend/src/test/java/org/cryptomator/hub/api/ConfigResourceTest.java b/backend/src/test/java/org/cryptomator/hub/api/ConfigResourceTest.java index 700a0e264..ce10fad7a 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/ConfigResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/ConfigResourceTest.java @@ -1,19 +1,11 @@ package org.cryptomator.hub.api; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; public class ConfigResourceTest { - ConfigResource configResource; - - @BeforeEach - void init() { - this.configResource = new ConfigResource(); - } - @ParameterizedTest @CsvSource({"foobar,foo,baz,bazbar", "foobar,baz,bar,foobar", @@ -22,7 +14,7 @@ void init() { "'',baz,bar,''" }) void testReplacePrefix(String str, String prefix, String replacement, String expected) { - String out = configResource.replacePrefix(str, prefix, replacement); + String out = ConfigResource.replacePrefix(str, prefix, replacement); Assertions.assertEquals(expected, out); } @@ -35,7 +27,7 @@ void testReplacePrefix(String str, String prefix, String replacement, String exp "/,''" }) void testTrimTrailingSlash(String in, String expected) { - String out = configResource.trimTrailingSlash(in); + String out = ConfigResource.trimTrailingSlash(in); Assertions.assertEquals(expected, out); } diff --git a/backend/src/test/java/org/cryptomator/hub/filters/ActiveLicenseFilterTest.java b/backend/src/test/java/org/cryptomator/hub/filters/ActiveLicenseFilterTest.java index 001a90f84..c85b14091 100644 --- a/backend/src/test/java/org/cryptomator/hub/filters/ActiveLicenseFilterTest.java +++ b/backend/src/test/java/org/cryptomator/hub/filters/ActiveLicenseFilterTest.java @@ -3,7 +3,6 @@ import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.core.Response; import org.cryptomator.hub.license.LicenseHolder; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatcher; @@ -12,17 +11,13 @@ public class ActiveLicenseFilterTest { ContainerRequestContext context = Mockito.mock(ContainerRequestContext.class); - ActiveLicenseFilter filter = new ActiveLicenseFilter(); - - @BeforeEach - void setup() { - filter.license = Mockito.mock(LicenseHolder.class); - } + LicenseHolder license = Mockito.mock(LicenseHolder.class); + ActiveLicenseFilter filter = new ActiveLicenseFilter(license); @Test @DisplayName("abort when providing expired license") void testFilterWithExpiredLicense() { - Mockito.doReturn(true).when(filter.license).isExpired(); + Mockito.doReturn(true).when(license).isExpired(); filter.filter(context); @@ -32,7 +27,7 @@ void testFilterWithExpiredLicense() { @Test @DisplayName("continue when seats are still available") void testDontFilterWhenLicenseIsNotExpired() { - Mockito.doReturn(false).when(filter.license).isExpired(); + Mockito.doReturn(false).when(license).isExpired(); filter.filter(context); diff --git a/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java b/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java index c30c71a69..6bbf40c07 100644 --- a/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java +++ b/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java @@ -36,15 +36,11 @@ class VaultRoleFilterTest { private final EffectiveVaultAccess.Repository effectiveVaultAccessRepo = Mockito.mock(EffectiveVaultAccess.Repository.class); private final EmergencyRecoveryProcess.Repository recoveryRepo = Mockito.mock(EmergencyRecoveryProcess.Repository.class); private final Vault.Repository vaultRepo = Mockito.mock(Vault.Repository.class); - private final VaultRoleFilter filter = new VaultRoleFilter(); + private final VaultRoleFilter filter = new VaultRoleFilter(jwt, effectiveVaultAccessRepo, recoveryRepo, vaultRepo); @BeforeEach void setup() { filter.resourceInfo = resourceInfo; - filter.jwt = jwt; - filter.effectiveVaultAccessRepo = effectiveVaultAccessRepo; - filter.recoveryRepo = recoveryRepo; - filter.vaultRepo = vaultRepo; Mockito.doReturn(uriInfo).when(context).getUriInfo(); Mockito.doReturn(securityContext).when(context).getSecurityContext(); diff --git a/backend/src/test/java/org/cryptomator/hub/keycloak/KeycloakAuthorityProviderTest.java b/backend/src/test/java/org/cryptomator/hub/keycloak/KeycloakAuthorityProviderTest.java index 218db70a0..86e291011 100644 --- a/backend/src/test/java/org/cryptomator/hub/keycloak/KeycloakAuthorityProviderTest.java +++ b/backend/src/test/java/org/cryptomator/hub/keycloak/KeycloakAuthorityProviderTest.java @@ -55,7 +55,7 @@ void setUp() { Mockito.when(user2.getEmail()).thenReturn("email3001"); Mockito.when(user2.isEnabled()).thenReturn(false); - keycloakRemoteUserProvider = new KeycloakAuthorityProvider(); + keycloakRemoteUserProvider = new KeycloakAuthorityProvider(null, null); } @Test diff --git a/backend/src/test/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPullerTest.java b/backend/src/test/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPullerTest.java index 7b3ad71b5..0568ba6b1 100644 --- a/backend/src/test/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPullerTest.java +++ b/backend/src/test/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPullerTest.java @@ -22,6 +22,7 @@ import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.converter.SimpleArgumentConverter; import org.junit.jupiter.params.provider.CsvSource; +import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.resource.GroupResource; import org.keycloak.admin.client.resource.GroupsResource; import org.keycloak.admin.client.resource.RealmResource; @@ -53,10 +54,12 @@ class KeycloakAuthorityPullerTest { + private final Keycloak keycloak = Mockito.mock(Keycloak.class); private final KeycloakAuthorityProvider remoteUserProvider = Mockito.mock(KeycloakAuthorityProvider.class); private final User.Repository userRepo = Mockito.mock(User.Repository.class); private final Group.Repository groupRepo = Mockito.mock(Group.Repository.class); private final EffectiveGroupMembership.Repository effectiveGroupMembershipRepo = Mockito.mock(EffectiveGroupMembership.Repository.class); + private final KeycloakRealmRoles realmRoles = Mockito.mock(KeycloakRealmRoles.class); private final List persistedUsers = new ArrayList<>(); private final List persistedGroups = new ArrayList<>(); @@ -65,11 +68,7 @@ class KeycloakAuthorityPullerTest { @BeforeEach void setUp() { - remoteUserPuller = new KeycloakAuthorityPuller(); - remoteUserPuller.remoteUserProvider = remoteUserProvider; - remoteUserPuller.userRepo = userRepo; - remoteUserPuller.groupRepo = groupRepo; - remoteUserPuller.effectiveGroupMembershipRepo = effectiveGroupMembershipRepo; + remoteUserPuller = new KeycloakAuthorityPuller(keycloak, userRepo, groupRepo, remoteUserProvider, effectiveGroupMembershipRepo, realmRoles, "cryptomator"); persistedUsers.clear(); Mockito.doAnswer(invocation -> { Iterable iterable = invocation.getArgument(0); @@ -372,12 +371,10 @@ class WriteMethods { private final RealmResource realm = Mockito.mock(RealmResource.class); private final UsersResource usersResource = Mockito.mock(UsersResource.class); private final GroupsResource groupsResource = Mockito.mock(GroupsResource.class); - private final KeycloakRealmRoles realmRoles = Mockito.mock(KeycloakRealmRoles.class); @BeforeEach void setUp() { remoteUserPuller.realm = realm; - remoteUserPuller.realmRoles = realmRoles; Mockito.lenient().when(realm.users()).thenReturn(usersResource); Mockito.lenient().when(realm.groups()).thenReturn(groupsResource); } @@ -530,7 +527,7 @@ void testSyncGroupUpsertsAndInvalidates() { Mockito.when(groupResource.toRepresentation()).thenReturn(representation); Mockito.when(groupResource.members(0, KeycloakAuthorityProvider.MAX_COUNT_PER_REQUEST)).thenReturn(List.of()); Mockito.when(groupRepo.findById("g")).thenReturn(null); - Mockito.when(userRepo.findByIds(Mockito.anyList())).thenReturn(Stream.of()); + Mockito.when(userRepo.streamByIds(Mockito.anyList())).thenReturn(Stream.of()); var result = remoteUserPuller.syncGroup("g"); @@ -561,13 +558,13 @@ void testSyncGroupPaginatesMembers() { Mockito.when(groupResource.members(0, KeycloakAuthorityProvider.MAX_COUNT_PER_REQUEST)).thenReturn(firstPage); Mockito.when(groupResource.members(KeycloakAuthorityProvider.MAX_COUNT_PER_REQUEST, KeycloakAuthorityProvider.MAX_COUNT_PER_REQUEST)).thenReturn(List.of(lastMember)); Mockito.when(groupRepo.findById("g")).thenReturn(null); - Mockito.when(userRepo.findByIds(Mockito.anyList())).thenReturn(Stream.of()); + Mockito.when(userRepo.streamByIds(Mockito.anyList())).thenReturn(Stream.of()); remoteUserPuller.syncGroup("g"); Mockito.verify(groupResource).members(KeycloakAuthorityProvider.MAX_COUNT_PER_REQUEST, KeycloakAuthorityProvider.MAX_COUNT_PER_REQUEST); var captor = ArgumentCaptor.forClass(List.class); - Mockito.verify(userRepo).findByIds(captor.capture()); + Mockito.verify(userRepo).streamByIds(captor.capture()); Assertions.assertEquals(KeycloakAuthorityProvider.MAX_COUNT_PER_REQUEST + 1, captor.getValue().size()); } @@ -726,7 +723,7 @@ void testCreateGroupSuccess() { Mockito.when(groupResource.toRepresentation()).thenReturn(representation); Mockito.when(groupResource.members(0, KeycloakAuthorityProvider.MAX_COUNT_PER_REQUEST)).thenReturn(List.of()); Mockito.when(groupRepo.findById("newGroup")).thenReturn(null); - Mockito.when(userRepo.findByIds(Mockito.anyList())).thenReturn(Stream.of()); + Mockito.when(userRepo.streamByIds(Mockito.anyList())).thenReturn(Stream.of()); var result = remoteUserPuller.createGroup("New Group", null); diff --git a/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderTest.java b/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderTest.java index d8210de77..75c5487e2 100644 --- a/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderTest.java +++ b/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderTest.java @@ -38,13 +38,11 @@ public class LicenseHolderTest { @BeforeEach public void resetTestclass() { - licenseHolder = new LicenseHolder(); - licenseHolder.licenseValidator = validator; - licenseHolder.settingsRepo = settingsRepo; - licenseHolder.randomSleeper = randomSleeper; - licenseHolder.licenseApi = licenseApi; - licenseHolder.managedApiUsername = Optional.empty(); - licenseHolder.managedApiPassword = Optional.empty(); + licenseHolder = buildLicenseHolder(Optional.empty(), Optional.empty()); + } + + private LicenseHolder buildLicenseHolder(Optional initialId, Optional initialLicenseToken) { + return new LicenseHolder(false, initialId, initialLicenseToken, Optional.empty(), Optional.empty(), validator, randomSleeper, settingsRepo, licenseApi); } @Nested @@ -65,8 +63,7 @@ void setup() { @DisplayName("call validateExistingLicense(), if DB contains existing token") void testValidateExistingLicense() { //to show check, that db has higher precedence - licenseHolderSpy.initialId = Optional.of("43"); - licenseHolderSpy.initialLicenseToken = Optional.of("initToken"); + licenseHolderSpy = Mockito.spy(buildLicenseHolder(Optional.of("43"), Optional.of("initToken"))); when(settings.getLicenseKey()).thenReturn("token"); when(settings.getHubId()).thenReturn("42"); var license = Mockito.mock(DecodedJWT.class); @@ -87,8 +84,7 @@ void testValidateExistingLicense() { "null, 42" }, nullValues = {"null"}) void testApplyInitLicense(String dbToken, String dbHubId) { - licenseHolderSpy.initialLicenseToken = Optional.of("token"); - licenseHolderSpy.initialId = Optional.of("43"); + licenseHolderSpy = Mockito.spy(buildLicenseHolder(Optional.of("43"), Optional.of("token"))); when(settings.getLicenseKey()).thenReturn(dbToken); when(settings.getHubId()).thenReturn(dbHubId); var license = Mockito.mock(DecodedJWT.class); @@ -109,8 +105,7 @@ void testApplyInitLicense(String dbToken, String dbHubId) { "dbToken, null, initToken, null" }, nullValues = {"null"}) void testRequestTrialLicense(String dbToken, String dbHubId, String initToken, String initId) { - licenseHolderSpy.initialLicenseToken = Optional.ofNullable(initToken); - licenseHolderSpy.initialId = Optional.ofNullable(initId); + licenseHolderSpy = Mockito.spy(buildLicenseHolder(Optional.ofNullable(initId), Optional.ofNullable(initToken))); when(settings.getLicenseKey()).thenReturn(dbToken); when(settings.getHubId()).thenReturn(dbHubId); var license = Mockito.mock(DecodedJWT.class); @@ -126,8 +121,7 @@ void testRequestTrialLicense(String dbToken, String dbHubId, String initToken, S @DisplayName("requestAnonTrialLicense() fails when server doesn't respond as expected") @Test void testFailingRequestTrialLicense() { - licenseHolderSpy.initialLicenseToken = Optional.empty(); - licenseHolderSpy.initialId = Optional.empty(); + licenseHolderSpy = Mockito.spy(buildLicenseHolder(Optional.empty(), Optional.empty())); doReturn(null).when(settings).getLicenseKey(); doReturn(null).when(settings).getHubId(); doCallRealMethod().when(licenseHolderSpy).requestAnonTrialLicense(Mockito.any()); diff --git a/backend/src/test/java/org/cryptomator/hub/license/LicenseValidatorTest.java b/backend/src/test/java/org/cryptomator/hub/license/LicenseValidatorTest.java index aa9ad6350..33f2d7802 100644 --- a/backend/src/test/java/org/cryptomator/hub/license/LicenseValidatorTest.java +++ b/backend/src/test/java/org/cryptomator/hub/license/LicenseValidatorTest.java @@ -32,13 +32,12 @@ class LicenseValidatorTest { private static final String LEAF_CERTIFICATE = "MIICDDCCAb6gAwIBAgIUDMOzxJY381JjdN82cW7qd+B3vh0wBQYDK2VwME4xCzAJBgNVBAYTAkRFMRYwFAYDVQQKDA1Ta3ltYXRpYyBHbWJIMScwJQYDVQQDDB5MaWNlbnNlIEludGVybWVkaWF0ZSBDQSAoVGVzdCkwHhcNMjYwMjI2MTU0MDM2WhcNMzYwMjI0MTU0MDM2WjBIMQswCQYDVQQGEwJERTEWMBQGA1UECgwNU2t5bWF0aWMgR21iSDEhMB8GA1UEAwwYTGljZW5zZSBJc3N1ZXIgQ0EgKFRlc3QpMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBiUVQ0HhZMuAOqiO2lPIT+MMSH4bcl6BOWnFn205bzTcRI9RuRdtrXVNwp/IPtjMVXTj/oW0r12HcrEdLmi9QI6QASTEByWLNTS/d94IoXmRYQTnC+RtH+H/4I1TWYw90aiig2yV0G1s0qCgAiyKswj+ST6r71NM/gepmlW3+qiv9/PWjQjBAMB0GA1UdDgQWBBSNjBwv+/iYQvpOOqz02u7xaASSITAfBgNVHSMEGDAWgBRMASsp1kiawJm8YoJ6+8/sq21X4zAFBgMrZXADQQA4Ok/+y0bdzm2RUmkHd6QFS6WbBKf9O4zz3Uc7iBMpKIq1kBlq+7TbbgMHJu+aYbODcRWT++5sx4i2OspkgOsJ"; private static final String LEAF_PRIVATE_KEY = "MGACAQAwEAYHKoZIzj0CAQYFK4EEACMESTBHAgEBBEIA+tBtqmK6OyXS+0ATPadXIF3mf1uwAY/ujIbhtox+dcqolusy8fR8cIVYNqbRb8wUZvbY++xn24nsDAiw6Za4NTg="; - LicenseValidator validator = new LicenseValidator(); + LicenseValidator validator; @BeforeEach void setup() { - var verifierProducer = new LicenseVerifierProducer(); - verifierProducer.licenseChainRequiredCn = ""; - validator.verifier = verifierProducer.produceLicenseVerifier(ROOT_CERTIFICATE, INTERMEDIATE_CN); + var verifierProducer = new LicenseVerifierProducer(""); + validator = new LicenseValidator(verifierProducer.produceLicenseVerifier(ROOT_CERTIFICATE, INTERMEDIATE_CN)); } @Test @@ -112,8 +111,8 @@ void testValidateTokenWithX5cCertificateChain() { @Test @DisplayName("reject token with invalid x5c certificate chain") void testRejectTokenWithInvalidX5cCertificateChain() { - var verifierProducer = new LicenseVerifierProducer(); - validator.verifier = verifierProducer.produceLicenseVerifier(ROOT_CERTIFICATE, INTERMEDIATE_CN); + var verifierProducer = new LicenseVerifierProducer(""); + validator = new LicenseValidator(verifierProducer.produceLicenseVerifier(ROOT_CERTIFICATE, INTERMEDIATE_CN)); var token = JWT.create() .withHeader(Map.of("x5c", List.of(LEAF_CERTIFICATE))) .withJWTId("42") @@ -145,8 +144,8 @@ void testValidateTokenWithMatchingIntermediateCn() { @Test @DisplayName("reject token with mismatching intermediate cert cn") void testRejectTokenWithMismatchingIntermediateCn() { - var verifierProducer = new LicenseVerifierProducer(); - validator.verifier = verifierProducer.produceLicenseVerifier(ROOT_CERTIFICATE, "some other CN"); + var verifierProducer = new LicenseVerifierProducer(""); + validator = new LicenseValidator(verifierProducer.produceLicenseVerifier(ROOT_CERTIFICATE, "some other CN")); var token = JWT.create() .withHeader(Map.of("x5c", List.of(LEAF_CERTIFICATE, INTERMEDIATE_CERTIFICATE))) .withJWTId("42") diff --git a/backend/src/test/resources/org/cryptomator/hub/flyway/V9999__Test_Data.sql b/backend/src/test/resources/org/cryptomator/hub/flyway/V9999__Test_Data.sql index 2f3ee4da9..85fab8afd 100644 --- a/backend/src/test/resources/org/cryptomator/hub/flyway/V9999__Test_Data.sql +++ b/backend/src/test/resources/org/cryptomator/hub/flyway/V9999__Test_Data.sql @@ -71,7 +71,8 @@ INSERT INTO "access_token" ("user_id", "vault_id", "vault_masterkey") VALUES ('user1', '7E57C0DE-0000-4000-8000-000100001111', 'jwe.jwe.jwe.vault1.user1'), -- direct access ('user2', '7E57C0DE-0000-4000-8000-000100001111', 'jwe.jwe.jwe.vault1.user2'), -- direct access - ('user1', '7E57C0DE-0000-4000-8000-000100002222', 'jwe.jwe.jwe.vault2.user1'); -- access via group1 + ('user1', '7E57C0DE-0000-4000-8000-000100002222', 'jwe.jwe.jwe.vault2.user1'), -- access via group1 + ('user1', '7E57C0DE-0000-4000-8000-00010000AAAA', 'jwe.jwe.jwe.vaultAAA.user1'); -- direct access to archived vault -- DEPRECATED: INSERT INTO "device_legacy" ("id", "owner_id", "name", "type", "publickey", "creation_time")