From 66f81b65cef9e776705596ca6992fe2bc150ad11 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 15 Mar 2024 16:57:03 +0100 Subject: [PATCH 01/54] renamed many tests to indicate those are integration tests --- backend/pom.xml | 13 +++++++++++++ ...rTest.java => KeycloakRemoteUserProviderIT.java} | 2 +- ...eUserPullerTest.java => RemoteUserPullerIT.java} | 2 +- ...LogResourceTest.java => AuditLogResourceIT.java} | 2 +- ...tyResourceTest.java => AuthorityResourceIT.java} | 4 +--- ...lingResourceTest.java => BillingResourceIT.java} | 2 +- ...t.java => BillingResourceManagedInstanceIT.java} | 4 ++-- ...eviceResourceTest.java => DeviceResourceIT.java} | 2 +- ...roupsResourceTest.java => GroupsResourceIT.java} | 4 +--- ...{UsersResourceTest.java => UsersResourceIT.java} | 2 +- ...{VaultResourceTest.java => VaultResourceIT.java} | 2 +- .../{EntityIntegrationTest.java => EntityIT.java} | 5 +---- ...{LicenseHolderTest.java => LicenseHolderIT.java} | 2 +- ...ationTestResourceTest.java => ValidationIT.java} | 2 +- 14 files changed, 27 insertions(+), 21 deletions(-) rename backend/src/test/java/org/cryptomator/hub/{KeycloakRemoteUserProviderTest.java => KeycloakRemoteUserProviderIT.java} (99%) rename backend/src/test/java/org/cryptomator/hub/{RemoteUserPullerTest.java => RemoteUserPullerIT.java} (99%) rename backend/src/test/java/org/cryptomator/hub/api/{AuditLogResourceTest.java => AuditLogResourceIT.java} (98%) rename backend/src/test/java/org/cryptomator/hub/api/{AuthorityResourceTest.java => AuthorityResourceIT.java} (97%) rename backend/src/test/java/org/cryptomator/hub/api/{BillingResourceTest.java => BillingResourceIT.java} (99%) rename backend/src/test/java/org/cryptomator/hub/api/{BillingResourceManagedInstanceTest.java => BillingResourceManagedInstanceIT.java} (93%) rename backend/src/test/java/org/cryptomator/hub/api/{DeviceResourceTest.java => DeviceResourceIT.java} (99%) rename backend/src/test/java/org/cryptomator/hub/api/{GroupsResourceTest.java => GroupsResourceIT.java} (95%) rename backend/src/test/java/org/cryptomator/hub/api/{UsersResourceTest.java => UsersResourceIT.java} (98%) rename backend/src/test/java/org/cryptomator/hub/api/{VaultResourceTest.java => VaultResourceIT.java} (99%) rename backend/src/test/java/org/cryptomator/hub/entities/{EntityIntegrationTest.java => EntityIT.java} (91%) rename backend/src/test/java/org/cryptomator/hub/license/{LicenseHolderTest.java => LicenseHolderIT.java} (99%) rename backend/src/test/java/org/cryptomator/hub/validation/{ValidationTestResourceTest.java => ValidationIT.java} (99%) diff --git a/backend/pom.xml b/backend/pom.xml index e151de29d..0bfbf6e66 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -17,6 +17,7 @@ eclipse-temurin:17-jre 4.4.0 3.1.2 + 3.2.5 @@ -174,6 +175,18 @@ + + maven-failsafe-plugin + ${failsafe-plugin.version} + + + + integration-test + verify + + + + diff --git a/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java b/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderIT.java similarity index 99% rename from backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java rename to backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderIT.java index f8b85c530..8842d4f1a 100644 --- a/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java +++ b/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderIT.java @@ -23,7 +23,7 @@ import java.util.Map; @QuarkusTest -class KeycloakRemoteUserProviderTest { +class KeycloakRemoteUserProviderIT { private RealmResource realm = Mockito.mock(RealmResource.class); private UsersResource usersResource = Mockito.mock(UsersResource.class); diff --git a/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java b/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerIT.java similarity index 99% rename from backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java rename to backend/src/test/java/org/cryptomator/hub/RemoteUserPullerIT.java index 3ef9525c2..ced3cc7f1 100644 --- a/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java +++ b/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerIT.java @@ -23,7 +23,7 @@ import java.util.stream.Collectors; @QuarkusTest -class RemoteUserPullerTest { +class RemoteUserPullerIT { private final RemoteUserProvider remoteUserProvider = Mockito.mock(RemoteUserProvider.class); private final User user = Mockito.mock(User.class); diff --git a/backend/src/test/java/org/cryptomator/hub/api/AuditLogResourceTest.java b/backend/src/test/java/org/cryptomator/hub/api/AuditLogResourceIT.java similarity index 98% rename from backend/src/test/java/org/cryptomator/hub/api/AuditLogResourceTest.java rename to backend/src/test/java/org/cryptomator/hub/api/AuditLogResourceIT.java index c128f55ef..43db2f146 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/AuditLogResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/AuditLogResourceIT.java @@ -14,7 +14,7 @@ @QuarkusTest @DisplayName("Resource /auditlog") -public class AuditLogResourceTest { +public class AuditLogResourceIT { @BeforeAll public static void beforeAll() { diff --git a/backend/src/test/java/org/cryptomator/hub/api/AuthorityResourceTest.java b/backend/src/test/java/org/cryptomator/hub/api/AuthorityResourceIT.java similarity index 97% rename from backend/src/test/java/org/cryptomator/hub/api/AuthorityResourceTest.java rename to backend/src/test/java/org/cryptomator/hub/api/AuthorityResourceIT.java index 129a3bf58..ef0544ea6 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/AuthorityResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/AuthorityResourceIT.java @@ -1,12 +1,10 @@ package org.cryptomator.hub.api; -import io.agroal.api.AgroalDataSource; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.security.TestSecurity; import io.quarkus.test.security.oidc.Claim; import io.quarkus.test.security.oidc.OidcSecurity; import io.restassured.RestAssured; -import jakarta.inject.Inject; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -21,7 +19,7 @@ @QuarkusTest @DisplayName("Resource /authorities") -public class AuthorityResourceTest { +public class AuthorityResourceIT { @BeforeAll public static void beforeAll() { diff --git a/backend/src/test/java/org/cryptomator/hub/api/BillingResourceTest.java b/backend/src/test/java/org/cryptomator/hub/api/BillingResourceIT.java similarity index 99% rename from backend/src/test/java/org/cryptomator/hub/api/BillingResourceTest.java rename to backend/src/test/java/org/cryptomator/hub/api/BillingResourceIT.java index d4c757857..20dcbe790 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/BillingResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/BillingResourceIT.java @@ -26,7 +26,7 @@ @QuarkusTest @DisplayName("Resource /billing") -public class BillingResourceTest { +public class BillingResourceIT { @InjectMock LicenseHolder licenseHolder; diff --git a/backend/src/test/java/org/cryptomator/hub/api/BillingResourceManagedInstanceTest.java b/backend/src/test/java/org/cryptomator/hub/api/BillingResourceManagedInstanceIT.java similarity index 93% rename from backend/src/test/java/org/cryptomator/hub/api/BillingResourceManagedInstanceTest.java rename to backend/src/test/java/org/cryptomator/hub/api/BillingResourceManagedInstanceIT.java index 58490902e..53d3b8483 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/BillingResourceManagedInstanceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/BillingResourceManagedInstanceIT.java @@ -28,8 +28,8 @@ @OidcSecurity(claims = { @Claim(key = "sub", value = "admin") }) -@TestProfile(BillingResourceManagedInstanceTest.ManagedInstanceTestProfile.class) -public class BillingResourceManagedInstanceTest { +@TestProfile(BillingResourceManagedInstanceIT.ManagedInstanceTestProfile.class) +public class BillingResourceManagedInstanceIT { @Inject AgroalDataSource dataSource; diff --git a/backend/src/test/java/org/cryptomator/hub/api/DeviceResourceTest.java b/backend/src/test/java/org/cryptomator/hub/api/DeviceResourceIT.java similarity index 99% rename from backend/src/test/java/org/cryptomator/hub/api/DeviceResourceTest.java rename to backend/src/test/java/org/cryptomator/hub/api/DeviceResourceIT.java index 491f3e5cb..b257a56ed 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/DeviceResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/DeviceResourceIT.java @@ -29,7 +29,7 @@ @QuarkusTest @DisplayName("Resource /devices") -public class DeviceResourceTest { +public class DeviceResourceIT { @Inject AgroalDataSource dataSource; diff --git a/backend/src/test/java/org/cryptomator/hub/api/GroupsResourceTest.java b/backend/src/test/java/org/cryptomator/hub/api/GroupsResourceIT.java similarity index 95% rename from backend/src/test/java/org/cryptomator/hub/api/GroupsResourceTest.java rename to backend/src/test/java/org/cryptomator/hub/api/GroupsResourceIT.java index 2ef886ceb..41325d7d5 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/GroupsResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/GroupsResourceIT.java @@ -6,7 +6,6 @@ import io.quarkus.test.security.oidc.Claim; import io.quarkus.test.security.oidc.OidcSecurity; import io.restassured.RestAssured; -import io.restassured.http.ContentType; import jakarta.inject.Inject; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; @@ -21,11 +20,10 @@ import static io.restassured.RestAssured.when; import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.empty; @QuarkusTest @DisplayName("Resource /groups") -public class GroupsResourceTest { +public class GroupsResourceIT { @Inject AgroalDataSource dataSource; diff --git a/backend/src/test/java/org/cryptomator/hub/api/UsersResourceTest.java b/backend/src/test/java/org/cryptomator/hub/api/UsersResourceIT.java similarity index 98% rename from backend/src/test/java/org/cryptomator/hub/api/UsersResourceTest.java rename to backend/src/test/java/org/cryptomator/hub/api/UsersResourceIT.java index 7ddc75737..4e79b6291 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/UsersResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/UsersResourceIT.java @@ -21,7 +21,7 @@ @QuarkusTest @DisplayName("Resource /users") -public class UsersResourceTest { +public class UsersResourceIT { @BeforeAll public static void beforeAll() { diff --git a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceTest.java b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java similarity index 99% rename from backend/src/test/java/org/cryptomator/hub/api/VaultResourceTest.java rename to backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java index 9d1601172..ed5e973d7 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java @@ -55,7 +55,7 @@ @QuarkusTest @DisplayName("Resource /vaults") -public class VaultResourceTest { +public class VaultResourceIT { @Inject AgroalDataSource dataSource; diff --git a/backend/src/test/java/org/cryptomator/hub/entities/EntityIntegrationTest.java b/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java similarity index 91% rename from backend/src/test/java/org/cryptomator/hub/entities/EntityIntegrationTest.java rename to backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java index a54560dee..9c7351c65 100644 --- a/backend/src/test/java/org/cryptomator/hub/entities/EntityIntegrationTest.java +++ b/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java @@ -4,19 +4,16 @@ import io.quarkus.test.TestTransaction; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; -import jakarta.persistence.PersistenceException; -import org.hibernate.exception.ConstraintViolationException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.sql.SQLException; -import java.time.Instant; import java.util.UUID; @QuarkusTest @DisplayName("Persistent Entities") -public class EntityIntegrationTest { +public class EntityIT { @Inject AgroalDataSource dataSource; diff --git a/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderTest.java b/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderIT.java similarity index 99% rename from backend/src/test/java/org/cryptomator/hub/license/LicenseHolderTest.java rename to backend/src/test/java/org/cryptomator/hub/license/LicenseHolderIT.java index 005e32586..a3db9160b 100644 --- a/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderTest.java +++ b/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderIT.java @@ -36,7 +36,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @QuarkusTest -public class LicenseHolderTest { +public class LicenseHolderIT { @Inject LicenseHolder holder; diff --git a/backend/src/test/java/org/cryptomator/hub/validation/ValidationTestResourceTest.java b/backend/src/test/java/org/cryptomator/hub/validation/ValidationIT.java similarity index 99% rename from backend/src/test/java/org/cryptomator/hub/validation/ValidationTestResourceTest.java rename to backend/src/test/java/org/cryptomator/hub/validation/ValidationIT.java index 33504ef23..1597c84d0 100644 --- a/backend/src/test/java/org/cryptomator/hub/validation/ValidationTestResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/validation/ValidationIT.java @@ -21,7 +21,7 @@ import static io.restassured.RestAssured.when; @QuarkusTest -public class ValidationTestResourceTest { +public class ValidationIT { private static final String[] MALICOUS_STRINGS = {"ยง$%&*", "", "\"; DELETE * FROM USERS;--", "\" src=\"http://evil.corp\""}; From 605d1dd5b094025d2b4112af56db8af1cf3703aa Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 15 Mar 2024 18:13:36 +0100 Subject: [PATCH 02/54] poc --- .../org/cryptomator/hub/RollbackTest.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 backend/src/test/java/org/cryptomator/hub/RollbackTest.java diff --git a/backend/src/test/java/org/cryptomator/hub/RollbackTest.java b/backend/src/test/java/org/cryptomator/hub/RollbackTest.java new file mode 100644 index 000000000..110e39f9d --- /dev/null +++ b/backend/src/test/java/org/cryptomator/hub/RollbackTest.java @@ -0,0 +1,86 @@ +package org.cryptomator.hub; + +import io.agroal.api.AgroalDataSource; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.flywaydb.core.Flyway; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.sql.SQLException; +import java.sql.Savepoint; +import java.util.concurrent.atomic.AtomicReference; + +@QuarkusTest +public class RollbackTest { + @Inject + Flyway flyway; + + @Inject + AgroalDataSource dataSource; + + @Nested + class WithFlywayCleanup { + + @BeforeEach + public void rollback() { + flyway.clean(); + flyway.migrate(); + } + + @Test + public void test1() throws SQLException { + try (var c = dataSource.getConnection(); var s = c.createStatement()) { + s.execute(""" + UPDATE "settings" + SET "hub_id" = '42', "license_key" = 'yodel' + WHERE "id" = 0; + """); + } + } + + @Test + public void test2() throws SQLException { + try (var c = dataSource.getConnection(); var s = c.createStatement()) { + var result = s.executeQuery(""" + SELECT * + FROM "settings" + WHERE "id" = 0; + """); + result.next(); + Assertions.assertNotEquals("yodel", result.getString("license_key")); + } + } + } + + @Nested + class NoCleanup { + + @Test + public void test1() throws SQLException { + try (var c = dataSource.getConnection(); var s = c.createStatement()) { + s.execute(""" + UPDATE "settings" + SET "hub_id" = '42', "license_key" = 'yodel' + WHERE "id" = 0; + """); + } + } + + @Test + public void test2() throws SQLException { + try (var c = dataSource.getConnection(); var s = c.createStatement()) { + var result = s.executeQuery(""" + SELECT * + FROM "settings" + WHERE "id" = 0; + """); + result.next(); + Assertions.assertEquals("yodel", result.getString("license_key")); + } + } + } +} From f2d139699951b202a86a156cff12b70d7d9bc5d2 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 16 Mar 2024 06:45:59 +0100 Subject: [PATCH 03/54] wrong property --- backend/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/pom.xml b/backend/pom.xml index 0bfbf6e66..9184ed96d 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -202,7 +202,7 @@ maven-failsafe-plugin - ${surefire-plugin.version} + ${failsafe-plugin.version} From 796ec5d57579ededa2f4ff923e65f27e30925ad1 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 16 Mar 2024 07:18:39 +0100 Subject: [PATCH 04/54] fix warning --- backend/src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 76d81e38d..af3d66e85 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -58,7 +58,7 @@ quarkus.datasource.db-kind=postgresql quarkus.datasource.jdbc.driver=org.postgresql.Driver quarkus.datasource.jdbc.transaction-requirement=off quarkus.datasource.jdbc.max-size=16 -quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQL10Dialect +quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQLDialect quarkus.hibernate-orm.database.globally-quoted-identifiers=true quarkus.flyway.migrate-at-start=true quarkus.flyway.locations=classpath:org/cryptomator/hub/flyway From cd47bfcc0bd943c6472a19664a7a47a6802a0ed9 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 16 Mar 2024 12:16:46 +0100 Subject: [PATCH 05/54] partially revert 66f81b6 --- ...eUserProviderIT.java => KeycloakRemoteUserProviderTest.java} | 2 +- .../hub/{RemoteUserPullerIT.java => RemoteUserPullerTest.java} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename backend/src/test/java/org/cryptomator/hub/{KeycloakRemoteUserProviderIT.java => KeycloakRemoteUserProviderTest.java} (99%) rename backend/src/test/java/org/cryptomator/hub/{RemoteUserPullerIT.java => RemoteUserPullerTest.java} (99%) diff --git a/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderIT.java b/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java similarity index 99% rename from backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderIT.java rename to backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java index 8842d4f1a..f8b85c530 100644 --- a/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderIT.java +++ b/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java @@ -23,7 +23,7 @@ import java.util.Map; @QuarkusTest -class KeycloakRemoteUserProviderIT { +class KeycloakRemoteUserProviderTest { private RealmResource realm = Mockito.mock(RealmResource.class); private UsersResource usersResource = Mockito.mock(UsersResource.class); diff --git a/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerIT.java b/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java similarity index 99% rename from backend/src/test/java/org/cryptomator/hub/RemoteUserPullerIT.java rename to backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java index ced3cc7f1..3ef9525c2 100644 --- a/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerIT.java +++ b/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java @@ -23,7 +23,7 @@ import java.util.stream.Collectors; @QuarkusTest -class RemoteUserPullerIT { +class RemoteUserPullerTest { private final RemoteUserProvider remoteUserProvider = Mockito.mock(RemoteUserProvider.class); private final User user = Mockito.mock(User.class); From 438888a4d135a3a7ec9381dd5314e3a8bbd42f9f Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 16 Mar 2024 12:19:09 +0100 Subject: [PATCH 06/54] suppress error during test execution: `Connection refused: localhost/127.0.0.1:8180` --- backend/src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index af3d66e85..c6078f01d 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -45,7 +45,7 @@ hub.keycloak.oidc.cryptomator-client-id=cryptomator %test.hub.keycloak.syncer-username=syncer %test.hub.keycloak.syncer-password=syncer %test.hub.keycloak.syncer-client-id=admin-cli -%test.hub.keycloak.syncer-period=1m +%test.hub.keycloak.syncer-period=off # Expose OpenAPI and SwaggerUI quarkus.swagger-ui.enable=false From 0f5dbb74850a09651058aaf70380ba82c9136e3e Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 16 Mar 2024 16:33:31 +0100 Subject: [PATCH 07/54] fix warning due to dynamically loaded java agent --- backend/pom.xml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/backend/pom.xml b/backend/pom.xml index afabf555b..75091cd53 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -7,7 +7,6 @@ 1.4.0-SNAPSHOT - 3.12.1 UTF-8 21 UTF-8 @@ -16,6 +15,8 @@ 3.8.2 eclipse-temurin:17-jre 4.4.0 + 3.12.1 + 3.6.1 3.2.3 3.2.5 @@ -153,10 +154,23 @@ ${project.jdk.version} + + maven-dependency-plugin + ${dependency-plugin.version} + + + jar-paths-to-properties + + properties + + + + maven-surefire-plugin ${surefire-plugin.version} + -javaagent:${net.bytebuddy:byte-buddy-agent:jar} org.jboss.logmanager.LogManager ${maven.home} @@ -166,6 +180,9 @@ maven-failsafe-plugin ${failsafe-plugin.version} + + -javaagent:${net.bytebuddy:byte-buddy-agent:jar} + From af50626cab590bc98dce3b9c3ef259968c85a57d Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 16 Mar 2024 16:35:09 +0100 Subject: [PATCH 08/54] remove unnecessary `@QuarkusTest` --- .../org/cryptomator/hub/RemoteUserPuller.java | 1 + .../hub/KeycloakRemoteUserProviderTest.java | 2 -- .../cryptomator/hub/RemoteUserPullerTest.java | 33 +++++++++---------- .../hub/filters/ActiveLicenseFilterTest.java | 2 -- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java b/backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java index 272ab6fd3..c691b6c15 100644 --- a/backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java +++ b/backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java @@ -85,6 +85,7 @@ void syncUpdatedGroups(Map keycloakGroups, Map dat dbGroup.members.addAll(diff(kcGroup.members, dbGroup.members)); dbGroup.members.removeAll(diff(dbGroup.members, kcGroup.members)); + // TODO why don't we run dbGroup.persist()? } } diff --git a/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java b/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java index f8b85c530..2e2096e66 100644 --- a/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java +++ b/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java @@ -1,6 +1,5 @@ package org.cryptomator.hub; -import io.quarkus.test.junit.QuarkusTest; import org.cryptomator.hub.entities.User; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -22,7 +21,6 @@ import java.util.List; import java.util.Map; -@QuarkusTest class KeycloakRemoteUserProviderTest { private RealmResource realm = Mockito.mock(RealmResource.class); diff --git a/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java b/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java index 3ef9525c2..e27586d45 100644 --- a/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java +++ b/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java @@ -1,6 +1,5 @@ package org.cryptomator.hub; -import io.quarkus.test.junit.QuarkusTest; import org.cryptomator.hub.entities.Authority; import org.cryptomator.hub.entities.Group; import org.cryptomator.hub.entities.User; @@ -16,13 +15,11 @@ import org.mockito.Mockito; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -@QuarkusTest class RemoteUserPullerTest { private final RemoteUserProvider remoteUserProvider = Mockito.mock(RemoteUserProvider.class); @@ -135,9 +132,9 @@ public void testUpdateUsers(@ConvertWith(StringArrayConverter.class) String[] ke for (String userId : updatedUserIds) { var kcUser = Mockito.mock(User.class); - Mockito.when(kcUser.pictureUrl).thenReturn(String.format("picture %s", userId)); - Mockito.when(kcUser.name).thenReturn(String.format("name %s", userId)); - Mockito.when(kcUser.email).thenReturn(String.format("email %s", userId)); + kcUser.pictureUrl = String.format("picture %s", userId); + kcUser.name = String.format("name %s", userId); + kcUser.email = String.format("email %s", userId); Mockito.when(keycloakUsers.get(userId)).thenReturn(kcUser); Mockito.when(databaseUsers.get(userId)).thenReturn(Mockito.mock(User.class)); @@ -146,10 +143,11 @@ public void testUpdateUsers(@ConvertWith(StringArrayConverter.class) String[] ke remoteUserPuller.syncUpdatedUsers(keycloakUsers, databaseUsers, deletedUserIds); for (String userId : updatedUserIds) { - Mockito.verify(databaseUsers.get(userId)).persist(); - Mockito.verify(databaseUsers.get(userId)).pictureUrl = String.format("picture %s", userId); - Mockito.verify(databaseUsers.get(userId)).name = String.format("name %s", userId); - Mockito.verify(databaseUsers.get(userId)).email = String.format("email %s", userId); + var dbUser = databaseUsers.get(userId); + Mockito.verify(dbUser).persist(); + Assertions.assertEquals(String.format("picture %s", userId), dbUser.pictureUrl); + Assertions.assertEquals(String.format("name %s", userId), dbUser.name); + Assertions.assertEquals(String.format("email %s", userId), dbUser.email); } } @@ -178,14 +176,12 @@ public void testUpdateGroups(@ConvertWith(StringArrayConverter.class) String[] k for (String groupId : updatedGroupIds) { var kcGroup = Mockito.mock(Group.class); - Mockito.when(kcGroup.name).thenReturn(String.format("name %s", groupId)); - Set kcMembers = new HashSet<>(Arrays.asList(user, otherKCUser)); - Mockito.when(kcGroup.members).thenReturn(kcMembers); + kcGroup.name = String.format("name %s", groupId); + kcGroup.members = Set.of(user, otherKCUser); var dbGroup = Mockito.mock(Group.class); - Mockito.when(dbGroup.name).thenReturn(String.format("name %s", groupId)); - Set dbMembers = new HashSet<>(Collections.singletonList(dbOnlyUser)); - Mockito.when(dbGroup.members).thenReturn(dbMembers); + dbGroup.name = String.format("name %s", groupId); + dbGroup.members = new HashSet<>(Set.of(dbOnlyUser)); Mockito.when(keycloakGroups.get(groupId)).thenReturn(kcGroup); Mockito.when(databaseGroups.get(groupId)).thenReturn(dbGroup); @@ -194,8 +190,9 @@ public void testUpdateGroups(@ConvertWith(StringArrayConverter.class) String[] k remoteUserPuller.syncUpdatedGroups(keycloakGroups, databaseGroups, deletedGroupIds); for (String groupId : updatedGroupIds) { - Mockito.verify(databaseGroups.get(groupId)).name = String.format("name %s", groupId); - Assertions.assertEquals(Set.of(user, otherKCUser), databaseGroups.get(groupId).members); + var dbGroup = databaseGroups.get(groupId); + Assertions.assertEquals(String.format("name %s", groupId), dbGroup.name); + Assertions.assertEquals(Set.of(user, otherKCUser), dbGroup.members); } } } 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 ce293f285..cc952481b 100644 --- a/backend/src/test/java/org/cryptomator/hub/filters/ActiveLicenseFilterTest.java +++ b/backend/src/test/java/org/cryptomator/hub/filters/ActiveLicenseFilterTest.java @@ -1,6 +1,5 @@ package org.cryptomator.hub.filters; -import io.quarkus.test.junit.QuarkusTest; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.core.Response; import org.cryptomator.hub.license.LicenseHolder; @@ -10,7 +9,6 @@ import org.mockito.ArgumentMatcher; import org.mockito.Mockito; -@QuarkusTest public class ActiveLicenseFilterTest { ContainerRequestContext context = Mockito.mock(ContainerRequestContext.class); From 9a58b80ae01cdc240d4f1643bc8ec78f5a960542 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 16 Mar 2024 21:16:51 +0100 Subject: [PATCH 09/54] split up LicenseHolderIT to avoid Quarkus restarts --- .../hub/license/InitialLicenseIT.java | 171 ++++++++++++++++++ .../hub/license/LicenseHolderIT.java | 161 ----------------- 2 files changed, 171 insertions(+), 161 deletions(-) create mode 100644 backend/src/test/java/org/cryptomator/hub/license/InitialLicenseIT.java diff --git a/backend/src/test/java/org/cryptomator/hub/license/InitialLicenseIT.java b/backend/src/test/java/org/cryptomator/hub/license/InitialLicenseIT.java new file mode 100644 index 000000000..81b872f7a --- /dev/null +++ b/backend/src/test/java/org/cryptomator/hub/license/InitialLicenseIT.java @@ -0,0 +1,171 @@ +package org.cryptomator.hub.license; + +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import io.quarkus.arc.Arc; +import io.quarkus.panache.mock.PanacheMock; +import io.quarkus.test.InjectMock; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import jakarta.inject.Inject; +import org.cryptomator.hub.entities.Settings; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Map; + +@QuarkusTest +@TestProfile(InitialLicenseIT.ValidInitPropsInstanceTestProfile.class) +public class InitialLicenseIT { + + @Inject + LicenseHolder holder; + + @InjectMock + LicenseValidator validator; + + Settings settings = Mockito.spy(new Settings()); + + @InjectMock + RandomMinuteSleeper randomMinuteSleeper; + + @BeforeEach + public void setup( ) throws InterruptedException { + Mockito.doNothing().when(randomMinuteSleeper).sleep(); + PanacheMock.mock(Settings.class); // see https://quarkus.io/guides/hibernate-orm-panache#mocking + Mockito.when(Settings.get()).thenReturn(settings); + Mockito.doNothing().when(settings).persistAndFlush(); + + // recreate bean to start with a fresh instance: + // init() implicitly called due to @PostConstruct which messes with our invocation count + // See https://github.com/cryptomator/hub/pull/229#discussion_r1374694626 for further information + try (var instance = Arc.container().instance(LicenseHolder.class)) { + if (instance.isAvailable()) { + instance.destroy(); + Assertions.assertDoesNotThrow(holder::isSet); // recreate + } + } + Mockito.clearInvocations(validator); + Mockito.clearInvocations(settings); + } + + @Nested + @DisplayName("If no token is stored in DB") + public class WithoutTokenInDatabase { + + @BeforeEach + public void setup() { + settings.hubId = "42"; + settings.licenseKey = null; + } + + @Test + @DisplayName("persist hub.initial-license if it is valid") + public void testValidInitTokenSet() { + var decodedJWT = Mockito.mock(DecodedJWT.class); + Mockito.when(validator.validate("initialToken", "42")).thenReturn(decodedJWT); + + holder.init(); + + Mockito.verify(validator).validate("initialToken", "42"); + Mockito.verify(settings).persistAndFlush(); + Assertions.assertEquals("initialToken", settings.licenseKey); + Assertions.assertEquals(decodedJWT, holder.get()); + } + + @Test + @DisplayName("no-op if hub.initial-license is invalid") + public void testInitTokenOnFailedValidationNotSet() { + Mockito.when(validator.validate("initialToken", "42")).thenThrow(new JWTVerificationException("")); + + holder.init(); + + Mockito.verify(validator).validate("initialToken", "42"); + Mockito.verify(settings, Mockito.never()).persistAndFlush(); + Assertions.assertNull(settings.licenseKey); + Assertions.assertNull(holder.get()); + } + + } + + @Nested + @DisplayName("If an existing token is stored in DB") + public class WithTokenInDatabase { + + @BeforeEach + public void setup() { + settings.hubId = "42"; + settings.licenseKey = "oldToken"; + } + + @Test + @DisplayName("valid hub.initial-license is ignored") + public void testValidDBTokenIgnoresValidInitToken() { + var decodedJWT = Mockito.mock(DecodedJWT.class); + Mockito.when(validator.validate("oldToken", "42")).thenReturn(decodedJWT); + settings.hubId = "42"; + settings.licenseKey = "oldToken"; + + holder.init(); + + Mockito.verify(validator, Mockito.never()).validate("initialToken", "42"); + Mockito.verify(validator).validate("oldToken", "42"); + PanacheMock.verify(Settings.class, Mockito.never()).persistAndFlush(); + Assertions.assertEquals("oldToken", settings.licenseKey); + Assertions.assertEquals(decodedJWT, holder.get()); + } + + @Test + @DisplayName("invalid hub.initial-license is ignored") + public void testValidDBTokenIgnoresInvalidInitToken() { + var decodedJWT = Mockito.mock(DecodedJWT.class); + Mockito.when(validator.validate("oldToken", "42")).thenReturn(decodedJWT); + settings.hubId = "42"; + settings.licenseKey = "oldToken"; + + holder.init(); + + Mockito.verify(validator, Mockito.never()).validate("initialToken", "42"); + Mockito.verify(validator).validate("oldToken", "42"); + Mockito.verify(settings, Mockito.never()).persistAndFlush(); + Assertions.assertEquals("oldToken", settings.licenseKey); + Assertions.assertEquals(decodedJWT, holder.get()); + } + + @Test + @DisplayName("explicitly setting a new token overwrites the existing one") + public void testSetValidToken() { + var decodedJWT = Mockito.mock(DecodedJWT.class); + Mockito.when(validator.validate("newToken", "42")).thenReturn(decodedJWT); + settings.hubId = "42"; + settings.licenseKey = "oldToken"; + + holder.set("newToken"); + + Mockito.verify(validator).validate("newToken", "42"); + Mockito.verify(settings).persistAndFlush(); + Assertions.assertEquals("newToken", settings.licenseKey); + Assertions.assertEquals(decodedJWT, holder.get()); + } + + + } + + + public static class ValidInitPropsInstanceTestProfile implements QuarkusTestProfile { + @Override + public Map getConfigOverrides() { + return Map.of("hub.initial-id", "42", "hub.initial-license", "initialToken"); + } + + @Override + public boolean disableGlobalTestResources() { + return true; + } + } +} diff --git a/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderIT.java b/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderIT.java index a3db9160b..f0cc8cc8a 100644 --- a/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderIT.java +++ b/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderIT.java @@ -5,8 +5,6 @@ import io.quarkus.arc.Arc; import io.quarkus.test.InjectMock; import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; import jakarta.inject.Inject; import org.cryptomator.hub.entities.Settings; import org.hibernate.Session; @@ -30,10 +28,8 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.concurrent.Flow; -import java.util.concurrent.atomic.AtomicBoolean; @QuarkusTest public class LicenseHolderIT { @@ -390,162 +386,5 @@ public void onComplete() { } } - @Nested - @TestProfile(LicenseHolderInitPropsTest.ValidInitPropsInstanceTestProfile.class) - @DisplayName("Testing LicenseHolder methods using InitProps") - class LicenseHolderInitPropsTest { - - @InjectMock - Session session; - - @InjectMock - LicenseValidator validator; - - @InjectMock - RandomMinuteSleeper randomMinuteSleeper; - - MockedStatic settingsClass; - - public static class ValidInitPropsInstanceTestProfile implements QuarkusTestProfile { - @Override - public Map getConfigOverrides() { - return Map.of("hub.initial-id", "42", "hub.initial-license", "token"); - } - } - - @BeforeEach - public void setup() throws InterruptedException { - Query mockQuery = Mockito.mock(Query.class); - Mockito.doNothing().when(session).persist(Mockito.any()); - Mockito.when(session.createQuery(Mockito.anyString())).thenReturn(mockQuery); - Mockito.when(mockQuery.getSingleResult()).thenReturn(0l); - Mockito.doNothing().when(randomMinuteSleeper).sleep(); - - Arc.container().instance(LicenseHolder.class).destroy(); - - settingsClass = Mockito.mockStatic(Settings.class); - } - - @AfterEach - public void teardown() { - settingsClass.close(); - } - - @Test - @DisplayName("If init token is valid, set it in license holder") - public void testValidInitTokenSet() { - var decodedJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(validator.validate("token", "42")).thenReturn(decodedJWT); - Settings settingsMock = new Settings(); - settingsMock.hubId = "42"; - settingsClass.when(Settings::get).thenReturn(settingsMock); - - var newLicensePersisted = new AtomicBoolean(false); - Mockito.doAnswer(invocation -> { - Settings settings = invocation.getArgument(0); - if (settings.hubId.equals("42") && settings.licenseKey.equals("token")) { - newLicensePersisted.set(true); - } - return null; - }).when(session).persist(Mockito.any()); - - holder.init(); - - // init implicitly called due to @PostConstruct which increases the times to verify by 1 - // See https://github.com/cryptomator/hub/pull/229#discussion_r1374694626 for further information - Mockito.verify(validator, Mockito.times(2)).validate("token", "42"); - Assertions.assertTrue(newLicensePersisted.get()); - Assertions.assertEquals(decodedJWT, holder.get()); - } - - @Test - @DisplayName("If init token is invalid and no token is set in db, do not modify db") - public void testInitTokenOnFailedValidationNotSet() { - Mockito.when(validator.validate("token", "42")).thenAnswer(invocationOnMock -> { - throw new JWTVerificationException(""); - }); - Settings settingsMock = new Settings(); - settingsMock.hubId = "42"; - settingsClass.when(Settings::get).thenReturn(settingsMock); - - holder.init(); - - // init implicitly called due to @PostConstruct which increases the times to verify by 1 - // See https://github.com/cryptomator/hub/pull/229#discussion_r1374694626 for further information - Mockito.verify(validator, Mockito.times(2)).validate("token", "42"); - Mockito.verify(session, Mockito.never()).persist(Mockito.eq(settingsMock)); - Assertions.assertNull(holder.get()); - } - - @Test - @DisplayName("If token is set in DB, ignore valid init token") - public void testValidDBTokenIgnoresValidInitToken() { - var decodedJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(validator.validate("token3000", "3000")).thenReturn(decodedJWT); - Settings settingsMock = new Settings(); - settingsMock.hubId = "3000"; - settingsMock.licenseKey = "token3000"; - settingsClass.when(Settings::get).thenReturn(settingsMock); - - holder.init(); - - // init implicitly called due to @PostConstruct which increases the times to verify by 1 - // See https://github.com/cryptomator/hub/pull/229#discussion_r1374694626 for further information - Mockito.verify(validator, Mockito.times(2)).validate("token3000", "3000"); - Mockito.verify(session, Mockito.never()).persist(Mockito.any()); - Assertions.assertEquals(decodedJWT, holder.get()); - } - - @Test - @DisplayName("If token is set in DB, ignore invalid init token") - public void testValidDBTokenIgnoresInvalidInitToken() { - Mockito.when(validator.validate("token", "42")).thenAnswer(invocationOnMock -> { - throw new JWTVerificationException(""); - }); - - var decodedJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(validator.validate("token3000", "42")).thenReturn(decodedJWT); - Settings settingsMock = new Settings(); - settingsMock.hubId = "42"; - settingsMock.licenseKey = "token3000"; - settingsClass.when(Settings::get).thenReturn(settingsMock); - - holder.init(); - - // init implicitly called due to @PostConstruct which increases the times to verify by 1 - // See https://github.com/cryptomator/hub/pull/229#discussion_r1374694626 for further information - Mockito.verify(validator, Mockito.times(2)).validate("token3000", "42"); - Mockito.verify(session, Mockito.never()).persist(Mockito.any()); - Assertions.assertEquals(decodedJWT, holder.get()); - } - - @Test - @DisplayName("Setting a valid token validates and overwrites the init token") - public void testSetValidToken() { - var decodedJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(validator.validate("token3000", "42")).thenReturn(decodedJWT); - - Settings initSettingsMock = new Settings(); - initSettingsMock.hubId = "42"; - settingsClass.when(Settings::get).thenReturn(initSettingsMock); - - var newLicensePersisted = new AtomicBoolean(false); - Mockito.doAnswer(invocation -> { - Settings settings = invocation.getArgument(0); - if (settings.hubId.equals("42") && settings.licenseKey.equals("token3000")) { - newLicensePersisted.set(true); - } - return null; - }).when(session).persist(Mockito.any()); - - holder.set("token3000"); - - Mockito.verify(validator, Mockito.times(1)).validate("token3000", "42"); - Assertions.assertTrue(newLicensePersisted.get()); - Assertions.assertEquals(decodedJWT, holder.get()); - } - - } - } From 18e35fec6461a5162aed519d856148af7a42cdcc Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 16 Mar 2024 21:32:40 +0100 Subject: [PATCH 10/54] use JUnit5StatelessTestsetInfoTreeReporter --- backend/pom.xml | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/backend/pom.xml b/backend/pom.xml index 7ff6d1753..8423028b8 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -19,6 +19,7 @@ 3.6.1 3.2.3 3.2.5 + 1.2.1 @@ -169,19 +170,61 @@ maven-surefire-plugin ${surefire-plugin.version} + + + me.fabriciorby + maven-surefire-junit5-tree-reporter + ${junit-tree-reporter.version} + + -javaagent:${net.bytebuddy:byte-buddy-agent:jar} org.jboss.logmanager.LogManager ${maven.home} + plain + + false + + + true + true + true + true + false + true + true + false + maven-failsafe-plugin ${failsafe-plugin.version} + + + me.fabriciorby + maven-surefire-junit5-tree-reporter + ${junit-tree-reporter.version} + + -javaagent:${net.bytebuddy:byte-buddy-agent:jar} + plain + + false + + + true + true + true + true + false + true + true + false + From 11f622dc6e0a5165e26fc5ec766a0a597bc5cd1d Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 08:54:48 +0100 Subject: [PATCH 11/54] add annotation for DB rollback after test --- .../org/cryptomator/hub/RollbackTest.java | 46 ++++++-------- .../cryptomator/hub/rollback/DBRollback.java | 11 ++++ .../hub/rollback/DBRollbackExtension.java | 60 +++++++++++++++++++ ...callback.QuarkusTestAfterConstructCallback | 1 + ...back.QuarkusTestAfterTestExecutionCallback | 1 + 5 files changed, 92 insertions(+), 27 deletions(-) create mode 100644 backend/src/test/java/org/cryptomator/hub/rollback/DBRollback.java create mode 100644 backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackExtension.java create mode 100644 backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback create mode 100644 backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterTestExecutionCallback diff --git a/backend/src/test/java/org/cryptomator/hub/RollbackTest.java b/backend/src/test/java/org/cryptomator/hub/RollbackTest.java index 110e39f9d..94fe4f85a 100644 --- a/backend/src/test/java/org/cryptomator/hub/RollbackTest.java +++ b/backend/src/test/java/org/cryptomator/hub/RollbackTest.java @@ -3,21 +3,18 @@ import io.agroal.api.AgroalDataSource; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; +import org.cryptomator.hub.rollback.DBRollback; import org.flywaydb.core.Flyway; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.sql.SQLException; -import java.sql.Savepoint; -import java.util.concurrent.atomic.AtomicReference; @QuarkusTest public class RollbackTest { @Inject - Flyway flyway; + public Flyway flyway; @Inject AgroalDataSource dataSource; @@ -25,20 +22,15 @@ public class RollbackTest { @Nested class WithFlywayCleanup { - @BeforeEach - public void rollback() { - flyway.clean(); - flyway.migrate(); - } - @Test + @DBRollback public void test1() throws SQLException { try (var c = dataSource.getConnection(); var s = c.createStatement()) { s.execute(""" - UPDATE "settings" - SET "hub_id" = '42', "license_key" = 'yodel' - WHERE "id" = 0; - """); + UPDATE "settings" + SET "hub_id" = '42', "license_key" = 'yodel' + WHERE "id" = 0; + """); } } @@ -46,10 +38,10 @@ public void test1() throws SQLException { public void test2() throws SQLException { try (var c = dataSource.getConnection(); var s = c.createStatement()) { var result = s.executeQuery(""" - SELECT * - FROM "settings" - WHERE "id" = 0; - """); + SELECT * + FROM "settings" + WHERE "id" = 0; + """); result.next(); Assertions.assertNotEquals("yodel", result.getString("license_key")); } @@ -63,10 +55,10 @@ class NoCleanup { public void test1() throws SQLException { try (var c = dataSource.getConnection(); var s = c.createStatement()) { s.execute(""" - UPDATE "settings" - SET "hub_id" = '42', "license_key" = 'yodel' - WHERE "id" = 0; - """); + UPDATE "settings" + SET "hub_id" = '42', "license_key" = 'yodel' + WHERE "id" = 0; + """); } } @@ -74,10 +66,10 @@ public void test1() throws SQLException { public void test2() throws SQLException { try (var c = dataSource.getConnection(); var s = c.createStatement()) { var result = s.executeQuery(""" - SELECT * - FROM "settings" - WHERE "id" = 0; - """); + SELECT * + FROM "settings" + WHERE "id" = 0; + """); result.next(); Assertions.assertEquals("yodel", result.getString("license_key")); } diff --git a/backend/src/test/java/org/cryptomator/hub/rollback/DBRollback.java b/backend/src/test/java/org/cryptomator/hub/rollback/DBRollback.java new file mode 100644 index 000000000..2d1416400 --- /dev/null +++ b/backend/src/test/java/org/cryptomator/hub/rollback/DBRollback.java @@ -0,0 +1,11 @@ +package org.cryptomator.hub.rollback; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DBRollback { +} \ No newline at end of file diff --git a/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackExtension.java b/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackExtension.java new file mode 100644 index 000000000..89bbdda2b --- /dev/null +++ b/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackExtension.java @@ -0,0 +1,60 @@ +package org.cryptomator.hub.rollback; + +import io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback; +import io.quarkus.test.junit.callback.QuarkusTestAfterTestExecutionCallback; +import io.quarkus.test.junit.callback.QuarkusTestMethodContext; +import org.flywaydb.core.Flyway; +import org.junit.jupiter.api.Nested; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; + +public class DBRollbackExtension implements QuarkusTestAfterConstructCallback, QuarkusTestAfterTestExecutionCallback { + + static final AtomicReference INSTANCE = new AtomicReference<>(null); + + @Override + public void afterTestExecution(QuarkusTestMethodContext context) { + var isAnnotationPresent = context.getTestMethod().getAnnotation(DBRollback.class) != null; + if(isAnnotationPresent) { + var flyway = INSTANCE.get(); + if(flyway == null) { + throw new IllegalStateException("Flyway instance was not set. Please ensure that test class (or enclosing class) have a public non-null, Flyway field."); + } + + flyway.clean(); + flyway.migrate(); + } + } + + @Override + public void afterConstruct(Object testInstance) { + try { + var topLevelTestInstance = getTopLevelTestInstance(testInstance); + INSTANCE.set(getFlywayObject(topLevelTestInstance)); + } catch (NoSuchFieldException | IllegalAccessException e) { + //no-op + } + } + + private Object getTopLevelTestInstance(Object testInstance) throws NoSuchFieldException, IllegalAccessException { + var testClazz = testInstance.getClass(); + var hasEnclosingClass = testClazz.getEnclosingClass() != null; + var isNotStatic = !Modifier.isStatic(testClazz.getModifiers()); + var isNested = testClazz.getAnnotation(Nested.class) != null; + if(hasEnclosingClass && isNotStatic && isNested) { + Field field = testClazz.getDeclaredField("this$0"); + field.setAccessible(true); + return getTopLevelTestInstance(field.get(testInstance)); + } else { + return testInstance; + } + } + + private Flyway getFlywayObject(Object obj) throws NoSuchFieldException, IllegalAccessException { + var flywayField = Arrays.stream(obj.getClass().getFields()).filter(field -> field.getType().equals(Flyway.class)).findFirst().orElseThrow(NoSuchFieldException::new); + return (Flyway) flywayField.get(obj); + } +} diff --git a/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback b/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback new file mode 100644 index 000000000..252d8cb65 --- /dev/null +++ b/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback @@ -0,0 +1 @@ +org.cryptomator.hub.rollback.DBRollbackExtension \ No newline at end of file diff --git a/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterTestExecutionCallback b/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterTestExecutionCallback new file mode 100644 index 000000000..252d8cb65 --- /dev/null +++ b/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterTestExecutionCallback @@ -0,0 +1 @@ +org.cryptomator.hub.rollback.DBRollbackExtension \ No newline at end of file From 35abdd60d65c1e8fed9273f4f91a822dbec92539 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 10:09:23 +0100 Subject: [PATCH 12/54] migrate AccessToken to repository pattern --- .../cryptomator/hub/api/UsersResource.java | 16 +++-- .../cryptomator/hub/api/VaultResource.java | 18 +++--- .../cryptomator/hub/entities/AccessToken.java | 60 ++++++++++++------- .../hub/entities/AccessTokenRepository.java | 26 ++++++++ .../cryptomator/hub/entities/EntityIT.java | 6 +- 5 files changed, 91 insertions(+), 35 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/AccessTokenRepository.java 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 1707d7730..50f1f807f 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java @@ -17,6 +17,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.AccessToken; +import org.cryptomator.hub.entities.AccessTokenRepository; import org.cryptomator.hub.entities.AuditEventVaultAccessGrant; import org.cryptomator.hub.entities.Device; import org.cryptomator.hub.entities.User; @@ -39,6 +40,9 @@ @Produces(MediaType.TEXT_PLAIN) public class UsersResource { + @Inject + AccessTokenRepository accessTokenRepo; + @Inject JsonWebToken jwt; @@ -82,14 +86,14 @@ public Response updateMyAccessTokens(@NotNull Map tokens) { if (vault == null) { continue; // skip } - var token = AccessToken.findById(new AccessToken.AccessId(user.id, vault.id)); + var token = accessTokenRepo.findById(new AccessToken.AccessId(user.id, vault.id)); if (token == null) { token = new AccessToken(); - token.vault = vault; - token.user = user; + token.setVault(vault); + token.setUser(user); } - token.vaultKey = entry.getValue(); - token.persist(); + token.setVaultKey(entry.getValue()); + accessTokenRepo.persist(token); AuditEventVaultAccessGrant.log(user.id, vault.id, user.id); } return Response.ok().build(); @@ -125,7 +129,7 @@ public Response resetMe() { user.setupCode = null; user.persist(); Device.deleteByOwner(user.id); - AccessToken.deleteByUser(user.id); + accessTokenRepo.deleteByUser(user.id); return Response.noContent().build(); } 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 f3bd2fa21..42082da87 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -31,6 +31,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.AccessToken; +import org.cryptomator.hub.entities.AccessTokenRepository; import org.cryptomator.hub.entities.AuditEventVaultAccessGrant; import org.cryptomator.hub.entities.AuditEventVaultCreate; import org.cryptomator.hub.entities.AuditEventVaultKeyRetrieve; @@ -72,6 +73,9 @@ @Path("/vaults") public class VaultResource { + @Inject + AccessTokenRepository accessTokenRepo; + @Inject JsonWebToken jwt; @@ -308,12 +312,12 @@ public Response unlock(@PathParam("vaultId") UUID vaultId, @QueryParam("evenIfAr throw new ActionRequiredException("User account not initialized."); } - var access = AccessToken.unlock(vaultId, jwt.getSubject()); + var access = accessTokenRepo.unlock(vaultId, jwt.getSubject()); if (access != null) { AuditEventVaultKeyRetrieve.log(jwt.getSubject(), vaultId, AuditEventVaultKeyRetrieve.Result.SUCCESS); var subscriptionStateHeaderName = "Hub-Subscription-State"; var subscriptionStateHeaderValue = license.isSet() ? "ACTIVE" : "INACTIVE"; // license expiration is not checked here, because it is checked in the ActiveLicense filter - return Response.ok(access.vaultKey).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); + return Response.ok(access.getVaultKey()).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); } else if (Vault.findById(vaultId) == null) { throw new NotFoundException("No such vault."); } else { @@ -350,14 +354,14 @@ public Response grantAccess(@PathParam("vaultId") UUID vaultId, @NotEmpty MapfindById(new AccessToken.AccessId(userId, vaultId)); + var token = accessTokenRepo.findById(new AccessToken.AccessId(userId, vaultId)); if (token == null) { token = new AccessToken(); - token.vault = vault; - token.user = User.findByIdOptional(userId).orElseThrow(NotFoundException::new); + token.setVault(vault); + token.setUser(User.findByIdOptional(userId).orElseThrow(NotFoundException::new)); } - token.vaultKey = entry.getValue(); - token.persist(); + token.setVaultKey(entry.getValue()); + accessTokenRepo.persist(token); AuditEventVaultAccessGrant.log(jwt.getSubject(), vaultId, userId); } return Response.ok().build(); 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 af6232d92..7f643f994 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java @@ -1,5 +1,6 @@ package org.cryptomator.hub.entities; +import io.quarkus.hibernate.orm.panache.PanacheEntity; import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import io.quarkus.panache.common.Parameters; import jakarta.persistence.CascadeType; @@ -27,36 +28,23 @@ WHERE token.id.vaultId = :vaultId AND token.id.userId = :userId """) @Table(name = "access_token") -public class AccessToken extends PanacheEntityBase { +public class AccessToken { @EmbeddedId - public AccessId id = new AccessId(); + AccessId id = new AccessId(); @ManyToOne(optional = false, cascade = {CascadeType.REMOVE}) @MapsId("userId") @JoinColumn(name = "user_id") - public User user; + User user; @ManyToOne(optional = false, cascade = {CascadeType.REMOVE}) @MapsId("vaultId") @JoinColumn(name = "vault_id") - public Vault vault; + Vault vault; @Column(name = "vault_masterkey", nullable = false) - public String vaultKey; - - public static AccessToken unlock(UUID vaultId, String userId) { - try { - return find("#AccessToken.get", Parameters.with("vaultId", vaultId).and("userId", userId)).firstResult(); - } catch (NoResultException e) { - return null; - } - } - - - public static void deleteByUser(String userId) { - delete("#AccessToken.deleteByUser", Parameters.with("userId", userId)); - } + String vaultKey; @Override public boolean equals(Object o) { @@ -69,6 +57,38 @@ public boolean equals(Object o) { && Objects.equals(vaultKey, other.vaultKey); } + public AccessId getId() { + return id; + } + + public void setId(AccessId id) { + this.id = id; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Vault getVault() { + return vault; + } + + public void setVault(Vault vault) { + this.vault = vault; + } + + public String getVaultKey() { + return vaultKey; + } + + public void setVaultKey(String vaultKey) { + this.vaultKey = vaultKey; + } + @Override public int hashCode() { return Objects.hash(id, user, vault, vaultKey); @@ -87,8 +107,8 @@ public String toString() { @Embeddable public static class AccessId implements Serializable { - public String userId; - public UUID vaultId; + String userId; + UUID vaultId; public AccessId(String userId, UUID vaultId) { this.userId = userId; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AccessTokenRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/AccessTokenRepository.java new file mode 100644 index 000000000..1c6e4b151 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/AccessTokenRepository.java @@ -0,0 +1,26 @@ +package org.cryptomator.hub.entities; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import io.quarkus.panache.common.Parameters; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.NoResultException; + +import java.util.UUID; + +@ApplicationScoped +public class AccessTokenRepository implements PanacheRepositoryBase { + + public AccessToken unlock(UUID vaultId, String userId) { + try { + return find("#AccessToken.get", Parameters.with("vaultId", vaultId).and("userId", userId)).firstResult(); + } catch (NoResultException e) { + return null; + } + } + + public void deleteByUser(String userId) { + delete("#AccessToken.deleteByUser", Parameters.with("userId", userId)); + } + +} diff --git a/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java b/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java index 9c7351c65..b58d1f122 100644 --- a/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java +++ b/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java @@ -15,6 +15,8 @@ @DisplayName("Persistent Entities") public class EntityIT { + @Inject + AccessTokenRepository accessTokenRepo; @Inject AgroalDataSource dataSource; @@ -32,7 +34,7 @@ public void removingUserCascadesToAccess() throws SQLException { } var deleted = User.deleteById("user999"); - var matchAfter = AccessToken.findAll().stream().anyMatch(a -> "user999".equals(a.user.id)); + var matchAfter = accessTokenRepo.findAll().stream().anyMatch(a -> "user999".equals(a.user.id)); Assertions.assertTrue(deleted); Assertions.assertFalse(matchAfter); } @@ -41,7 +43,7 @@ public void removingUserCascadesToAccess() throws SQLException { @TestTransaction @DisplayName("Retrieve the correct token when a user has access to multiple vaults") public void testGetCorrectTokenForDeviceWithAcessToMultipleVaults() { - var token = AccessToken.unlock(UUID.fromString("7E57C0DE-0000-4000-8000-000100001111"), "user1"); + var token = accessTokenRepo.unlock(UUID.fromString("7E57C0DE-0000-4000-8000-000100001111"), "user1"); Assertions.assertEquals(UUID.fromString("7E57C0DE-0000-4000-8000-000100001111"), token.vault.id); Assertions.assertEquals("user1", token.user.id); Assertions.assertEquals("jwe.jwe.jwe.vault1.user1", token.vaultKey); From b1f494060737cdac29ef81a30bfb4259b5efa716 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 10:22:12 +0100 Subject: [PATCH 13/54] reformat named query --- .../org/cryptomator/hub/entities/AccessToken.java | 12 ++++++------ .../hub/entities/AccessTokenRepository.java | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) 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 7f643f994..646fa99a1 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java @@ -1,8 +1,5 @@ package org.cryptomator.hub.entities; -import io.quarkus.hibernate.orm.panache.PanacheEntity; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.panache.common.Parameters; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; @@ -12,7 +9,6 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.MapsId; import jakarta.persistence.NamedQuery; -import jakarta.persistence.NoResultException; import jakarta.persistence.Table; import java.io.Serializable; @@ -20,14 +16,18 @@ import java.util.UUID; @Entity -@NamedQuery(name = "AccessToken.deleteByUser", query = "DELETE FROM AccessToken a WHERE a.id.userId = :userId") +@Table(name = "access_token") +@NamedQuery(name = "AccessToken.deleteByUser", query = """ + DELETE + FROM AccessToken a + WHERE a.id.userId = :userId + """) @NamedQuery(name = "AccessToken.get", query = """ SELECT token FROM AccessToken token INNER JOIN EffectiveVaultAccess perm ON token.id.vaultId = perm.id.vaultId AND token.id.userId = perm.id.authorityId WHERE token.id.vaultId = :vaultId AND token.id.userId = :userId """) -@Table(name = "access_token") public class AccessToken { @EmbeddedId diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AccessTokenRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/AccessTokenRepository.java index 1c6e4b151..029489283 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AccessTokenRepository.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/AccessTokenRepository.java @@ -1,6 +1,5 @@ package org.cryptomator.hub.entities; -import io.quarkus.hibernate.orm.panache.PanacheRepository; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; From 7f1f58d932228868f4506bf8995b12b2c1bc277c Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 14:31:39 +0100 Subject: [PATCH 14/54] Moved and renamed Audit event entities --- .../cryptomator/hub/api/AuditLogResource.java | 103 +++++++++--------- .../cryptomator/hub/api/DeviceResource.java | 8 +- .../cryptomator/hub/api/UsersResource.java | 5 +- .../cryptomator/hub/api/VaultResource.java | 44 ++++---- .../hub/entities/{ => events}/AuditEvent.java | 51 +++++---- .../entities/events/AuditEventRepository.java | 29 +++++ .../DeviceRegisteredEvent.java} | 19 ++-- .../DeviceRemovedEvent.java} | 10 +- .../VaultAccessGrantedEvent.java} | 10 +- .../VaultCreatedEvent.java} | 10 +- .../VaultKeyRetrievedEvent.java} | 11 +- .../VaultMemberAddedEvent.java} | 11 +- .../VaultMemberRemovedEvent.java} | 10 +- .../VaultMemberUpdatedEvent.java} | 11 +- .../VaultOwnershipClaimedEvent.java} | 10 +- .../VaultUpdatedEvent.java} | 10 +- 16 files changed, 197 insertions(+), 155 deletions(-) rename backend/src/main/java/org/cryptomator/hub/entities/{ => events}/AuditEvent.java (69%) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/AuditEventRepository.java rename backend/src/main/java/org/cryptomator/hub/entities/{AuditEventDeviceRegister.java => events/DeviceRegisteredEvent.java} (78%) rename backend/src/main/java/org/cryptomator/hub/entities/{AuditEventDeviceRemove.java => events/DeviceRemovedEvent.java} (79%) rename backend/src/main/java/org/cryptomator/hub/entities/{AuditEventVaultAccessGrant.java => events/VaultAccessGrantedEvent.java} (81%) rename backend/src/main/java/org/cryptomator/hub/entities/{AuditEventVaultCreate.java => events/VaultCreatedEvent.java} (84%) rename backend/src/main/java/org/cryptomator/hub/entities/{AuditEventVaultKeyRetrieve.java => events/VaultKeyRetrievedEvent.java} (82%) rename backend/src/main/java/org/cryptomator/hub/entities/{AuditEventVaultMemberAdd.java => events/VaultMemberAddedEvent.java} (82%) rename backend/src/main/java/org/cryptomator/hub/entities/{AuditEventVaultMemberRemove.java => events/VaultMemberRemovedEvent.java} (81%) rename backend/src/main/java/org/cryptomator/hub/entities/{AuditEventVaultMemberUpdate.java => events/VaultMemberUpdatedEvent.java} (82%) rename backend/src/main/java/org/cryptomator/hub/entities/{AuditEventVaultOwnershipClaim.java => events/VaultOwnershipClaimedEvent.java} (77%) rename backend/src/main/java/org/cryptomator/hub/entities/{AuditEventVaultUpdate.java => events/VaultUpdatedEvent.java} (86%) 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 65a165f1c..d1ceed896 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -12,17 +12,18 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; -import org.cryptomator.hub.entities.AuditEvent; -import org.cryptomator.hub.entities.AuditEventDeviceRegister; -import org.cryptomator.hub.entities.AuditEventDeviceRemove; -import org.cryptomator.hub.entities.AuditEventVaultAccessGrant; -import org.cryptomator.hub.entities.AuditEventVaultCreate; -import org.cryptomator.hub.entities.AuditEventVaultKeyRetrieve; -import org.cryptomator.hub.entities.AuditEventVaultMemberAdd; -import org.cryptomator.hub.entities.AuditEventVaultMemberRemove; -import org.cryptomator.hub.entities.AuditEventVaultMemberUpdate; -import org.cryptomator.hub.entities.AuditEventVaultOwnershipClaim; -import org.cryptomator.hub.entities.AuditEventVaultUpdate; +import org.cryptomator.hub.entities.events.AuditEvent; +import org.cryptomator.hub.entities.events.DeviceRegisteredEvent; +import org.cryptomator.hub.entities.events.DeviceRemovedEvent; +import org.cryptomator.hub.entities.events.AuditEventRepository; +import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent; +import org.cryptomator.hub.entities.events.VaultCreatedEvent; +import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; +import org.cryptomator.hub.entities.events.VaultMemberAddedEvent; +import org.cryptomator.hub.entities.events.VaultMemberRemovedEvent; +import org.cryptomator.hub.entities.events.VaultMemberUpdatedEvent; +import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEvent; +import org.cryptomator.hub.entities.events.VaultUpdatedEvent; import org.cryptomator.hub.entities.Device; import org.cryptomator.hub.entities.VaultAccess; import org.cryptomator.hub.license.LicenseHolder; @@ -38,6 +39,8 @@ @Path("/auditlog") public class AuditLogResource { + @Inject + AuditEventRepository auditEventRepo; @Inject LicenseHolder license; @@ -71,21 +74,21 @@ public List getAllEvents(@QueryParam("startDate") Instant startDa throw new BadRequestException("paginationId must be specified"); } - return AuditEvent.findAllInPeriod(startDate, endDate, paginationId, order.equals("asc"), pageSize).map(AuditEventDto::fromEntity).toList(); + return auditEventRepo.findAllInPeriod(startDate, endDate, paginationId, order.equals("asc"), pageSize).map(AuditEventDto::fromEntity).toList(); } @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ // - @JsonSubTypes.Type(value = AuditEventDeviceRegisterDto.class, name = AuditEventDeviceRegister.TYPE), // - @JsonSubTypes.Type(value = AuditEventDeviceRemoveDto.class, name = AuditEventDeviceRemove.TYPE), // - @JsonSubTypes.Type(value = AuditEventVaultCreateDto.class, name = AuditEventVaultCreate.TYPE), // - @JsonSubTypes.Type(value = AuditEventVaultUpdateDto.class, name = AuditEventVaultUpdate.TYPE), // - @JsonSubTypes.Type(value = AuditEventVaultAccessGrantDto.class, name = AuditEventVaultAccessGrant.TYPE), // - @JsonSubTypes.Type(value = AuditEventVaultKeyRetrieveDto.class, name = AuditEventVaultKeyRetrieve.TYPE), // - @JsonSubTypes.Type(value = AuditEventVaultMemberAddDto.class, name = AuditEventVaultMemberAdd.TYPE), // - @JsonSubTypes.Type(value = AuditEventVaultMemberRemoveDto.class, name = AuditEventVaultMemberRemove.TYPE), // - @JsonSubTypes.Type(value = AuditEventVaultMemberUpdateDto.class, name = AuditEventVaultMemberUpdate.TYPE), // - @JsonSubTypes.Type(value = AuditEventVaultOwnershipClaimDto.class, name = AuditEventVaultOwnershipClaim.TYPE) // + @JsonSubTypes.Type(value = DeviceRegisteredEventDto.class, name = DeviceRegisteredEvent.TYPE), // + @JsonSubTypes.Type(value = DeviceRemovedEventDto.class, name = DeviceRemovedEvent.TYPE), // + @JsonSubTypes.Type(value = VaultCreatedEventDto.class, name = VaultCreatedEvent.TYPE), // + @JsonSubTypes.Type(value = VaultUpdatedEventDto.class, name = VaultUpdatedEvent.TYPE), // + @JsonSubTypes.Type(value = VaultAccessGrantedEventDto.class, name = VaultAccessGrantedEvent.TYPE), // + @JsonSubTypes.Type(value = VaultKeyRetrievedEventDto.class, name = VaultKeyRetrievedEvent.TYPE), // + @JsonSubTypes.Type(value = VaultMemberAddedEventDto.class, name = VaultMemberAddedEvent.TYPE), // + @JsonSubTypes.Type(value = VaultMemberRemovedEventDto.class, name = VaultMemberRemovedEvent.TYPE), // + @JsonSubTypes.Type(value = VaultMemberUpdatedEventDto.class, name = VaultMemberUpdatedEvent.TYPE), // + @JsonSubTypes.Type(value = VaultOwnershipClaimedEventDto.class, name = VaultOwnershipClaimedEvent.TYPE) // }) public interface AuditEventDto { @@ -97,57 +100,57 @@ public interface AuditEventDto { static AuditEventDto fromEntity(AuditEvent entity) { return switch (entity) { - case AuditEventDeviceRegister evt -> new AuditEventDeviceRegisterDto(evt.id, evt.timestamp, AuditEventDeviceRegister.TYPE, evt.registeredBy, evt.deviceId, evt.deviceName, evt.deviceType); - case AuditEventDeviceRemove evt -> new AuditEventDeviceRemoveDto(evt.id, evt.timestamp, AuditEventDeviceRemove.TYPE, evt.removedBy, evt.deviceId); - case AuditEventVaultCreate evt -> new AuditEventVaultCreateDto(evt.id, evt.timestamp, AuditEventVaultCreate.TYPE, evt.createdBy, evt.vaultId, evt.vaultName, evt.vaultDescription); - case AuditEventVaultUpdate evt -> new AuditEventVaultUpdateDto(evt.id, evt.timestamp, AuditEventVaultUpdate.TYPE, evt.updatedBy, evt.vaultId, evt.vaultName, evt.vaultDescription, evt.vaultArchived); - case AuditEventVaultAccessGrant evt -> new AuditEventVaultAccessGrantDto(evt.id, evt.timestamp, AuditEventVaultAccessGrant.TYPE, evt.grantedBy, evt.vaultId, evt.authorityId); - case AuditEventVaultKeyRetrieve evt -> new AuditEventVaultKeyRetrieveDto(evt.id, evt.timestamp, AuditEventVaultKeyRetrieve.TYPE, evt.retrievedBy, evt.vaultId, evt.result); - case AuditEventVaultMemberAdd evt -> new AuditEventVaultMemberAddDto(evt.id, evt.timestamp, AuditEventVaultMemberAdd.TYPE, evt.addedBy, evt.vaultId, evt.authorityId, evt.role); - case AuditEventVaultMemberRemove evt -> new AuditEventVaultMemberRemoveDto(evt.id, evt.timestamp, AuditEventVaultMemberRemove.TYPE, evt.removedBy, evt.vaultId, evt.authorityId); - case AuditEventVaultMemberUpdate evt -> new AuditEventVaultMemberUpdateDto(evt.id, evt.timestamp, AuditEventVaultMemberUpdate.TYPE, evt.updatedBy, evt.vaultId, evt.authorityId, evt.role); - case AuditEventVaultOwnershipClaim evt -> new AuditEventVaultOwnershipClaimDto(evt.id, evt.timestamp, AuditEventVaultOwnershipClaim.TYPE, evt.claimedBy, evt.vaultId); + case DeviceRegisteredEvent evt -> new DeviceRegisteredEventDto(evt.getId(), evt.getTimestamp(), DeviceRegisteredEvent.TYPE, evt.registeredBy, evt.deviceId, evt.deviceName, evt.deviceType); + case DeviceRemovedEvent evt -> new DeviceRemovedEventDto(evt.getId(), evt.getTimestamp(), DeviceRemovedEvent.TYPE, evt.removedBy, evt.deviceId); + case VaultCreatedEvent evt -> new VaultCreatedEventDto(evt.getId(), evt.getTimestamp(), VaultCreatedEvent.TYPE, evt.createdBy, evt.vaultId, evt.vaultName, evt.vaultDescription); + case VaultUpdatedEvent evt -> new VaultUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultUpdatedEvent.TYPE, evt.updatedBy, evt.vaultId, evt.vaultName, evt.vaultDescription, evt.vaultArchived); + case VaultAccessGrantedEvent evt -> new VaultAccessGrantedEventDto(evt.getId(), evt.getTimestamp(), VaultAccessGrantedEvent.TYPE, evt.grantedBy, evt.vaultId, evt.authorityId); + case VaultKeyRetrievedEvent evt -> new VaultKeyRetrievedEventDto(evt.getId(), evt.getTimestamp(), VaultKeyRetrievedEvent.TYPE, evt.retrievedBy, evt.vaultId, evt.result); + case VaultMemberAddedEvent evt -> new VaultMemberAddedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberAddedEvent.TYPE, evt.addedBy, evt.vaultId, evt.authorityId, evt.role); + case VaultMemberRemovedEvent evt -> new VaultMemberRemovedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberRemovedEvent.TYPE, evt.removedBy, evt.vaultId, evt.authorityId); + case VaultMemberUpdatedEvent evt -> new VaultMemberUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberUpdatedEvent.TYPE, evt.updatedBy, evt.vaultId, evt.authorityId, evt.role); + case VaultOwnershipClaimedEvent evt -> new VaultOwnershipClaimedEventDto(evt.getId(), evt.getTimestamp(), VaultOwnershipClaimedEvent.TYPE, evt.claimedBy, evt.vaultId); default -> throw new UnsupportedOperationException("conversion not implemented for event type " + entity.getClass()); }; } } - record AuditEventDeviceRegisterDto(long id, Instant timestamp, String type, @JsonProperty("registeredBy") String registeredBy, @JsonProperty("deviceId") String deviceId, @JsonProperty("deviceName") String deviceName, - @JsonProperty("deviceType") Device.Type deviceType) implements AuditEventDto { + record DeviceRegisteredEventDto(long id, Instant timestamp, String type, @JsonProperty("registeredBy") String registeredBy, @JsonProperty("deviceId") String deviceId, @JsonProperty("deviceName") String deviceName, + @JsonProperty("deviceType") Device.Type deviceType) implements AuditEventDto { } - record AuditEventDeviceRemoveDto(long id, Instant timestamp, String type, @JsonProperty("removedBy") String removedBy, @JsonProperty("deviceId") String deviceId) implements AuditEventDto { + record DeviceRemovedEventDto(long id, Instant timestamp, String type, @JsonProperty("removedBy") String removedBy, @JsonProperty("deviceId") String deviceId) implements AuditEventDto { } - record AuditEventVaultCreateDto(long id, Instant timestamp, String type, @JsonProperty("createdBy") String createdBy, @JsonProperty("vaultId") UUID vaultId, @JsonProperty("vaultName") String vaultName, - @JsonProperty("vaultDescription") String vaultDescription) implements AuditEventDto { + record VaultCreatedEventDto(long id, Instant timestamp, String type, @JsonProperty("createdBy") String createdBy, @JsonProperty("vaultId") UUID vaultId, @JsonProperty("vaultName") String vaultName, + @JsonProperty("vaultDescription") String vaultDescription) implements AuditEventDto { } - record AuditEventVaultUpdateDto(long id, Instant timestamp, String type, @JsonProperty("updatedBy") String updatedBy, @JsonProperty("vaultId") UUID vaultId, @JsonProperty("vaultName") String vaultName, - @JsonProperty("vaultDescription") String vaultDescription, @JsonProperty("vaultArchived") boolean vaultArchived) implements AuditEventDto { + record VaultUpdatedEventDto(long id, Instant timestamp, String type, @JsonProperty("updatedBy") String updatedBy, @JsonProperty("vaultId") UUID vaultId, @JsonProperty("vaultName") String vaultName, + @JsonProperty("vaultDescription") String vaultDescription, @JsonProperty("vaultArchived") boolean vaultArchived) implements AuditEventDto { } - record AuditEventVaultAccessGrantDto(long id, Instant timestamp, String type, @JsonProperty("grantedBy") String grantedBy, @JsonProperty("vaultId") UUID vaultId, - @JsonProperty("authorityId") String authorityId) implements AuditEventDto { + record VaultAccessGrantedEventDto(long id, Instant timestamp, String type, @JsonProperty("grantedBy") String grantedBy, @JsonProperty("vaultId") UUID vaultId, + @JsonProperty("authorityId") String authorityId) implements AuditEventDto { } - record AuditEventVaultKeyRetrieveDto(long id, Instant timestamp, String type, @JsonProperty("retrievedBy") String retrievedBy, @JsonProperty("vaultId") UUID vaultId, - @JsonProperty("result") AuditEventVaultKeyRetrieve.Result result) implements AuditEventDto { + record VaultKeyRetrievedEventDto(long id, Instant timestamp, String type, @JsonProperty("retrievedBy") String retrievedBy, @JsonProperty("vaultId") UUID vaultId, + @JsonProperty("result") VaultKeyRetrievedEvent.Result result) implements AuditEventDto { } - record AuditEventVaultMemberAddDto(long id, Instant timestamp, String type, @JsonProperty("addedBy") String addedBy, @JsonProperty("vaultId") UUID vaultId, @JsonProperty("authorityId") String authorityId, - @JsonProperty("role") VaultAccess.Role role) implements AuditEventDto { + record VaultMemberAddedEventDto(long id, Instant timestamp, String type, @JsonProperty("addedBy") String addedBy, @JsonProperty("vaultId") UUID vaultId, @JsonProperty("authorityId") String authorityId, + @JsonProperty("role") VaultAccess.Role role) implements AuditEventDto { } - record AuditEventVaultMemberRemoveDto(long id, Instant timestamp, String type, @JsonProperty("removedBy") String removedBy, @JsonProperty("vaultId") UUID vaultId, - @JsonProperty("authorityId") String authorityId) implements AuditEventDto { + record VaultMemberRemovedEventDto(long id, Instant timestamp, String type, @JsonProperty("removedBy") String removedBy, @JsonProperty("vaultId") UUID vaultId, + @JsonProperty("authorityId") String authorityId) implements AuditEventDto { } - record AuditEventVaultMemberUpdateDto(long id, Instant timestamp, String type, @JsonProperty("updatedBy") String updatedBy, @JsonProperty("vaultId") UUID vaultId, @JsonProperty("authorityId") String authorityId, - @JsonProperty("role") VaultAccess.Role role) implements AuditEventDto { + record VaultMemberUpdatedEventDto(long id, Instant timestamp, String type, @JsonProperty("updatedBy") String updatedBy, @JsonProperty("vaultId") UUID vaultId, @JsonProperty("authorityId") String authorityId, + @JsonProperty("role") VaultAccess.Role role) implements AuditEventDto { } - record AuditEventVaultOwnershipClaimDto(long id, Instant timestamp, String type, @JsonProperty("claimedBy") String claimedBy, @JsonProperty("vaultId") UUID vaultId) implements AuditEventDto { + record VaultOwnershipClaimedEventDto(long id, Instant timestamp, String type, @JsonProperty("claimedBy") String claimedBy, @JsonProperty("vaultId") UUID vaultId) implements AuditEventDto { } } 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 076447e22..7739c6890 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java @@ -20,8 +20,8 @@ import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.cryptomator.hub.entities.AuditEventDeviceRegister; -import org.cryptomator.hub.entities.AuditEventDeviceRemove; +import org.cryptomator.hub.entities.events.DeviceRegisteredEvent; +import org.cryptomator.hub.entities.events.DeviceRemovedEvent; import org.cryptomator.hub.entities.Device; import org.cryptomator.hub.entities.LegacyAccessToken; import org.cryptomator.hub.entities.LegacyDevice; @@ -95,7 +95,7 @@ public Response createOrUpdate(@Valid @NotNull DeviceDto dto, @PathParam("device try { device.persistAndFlush(); - AuditEventDeviceRegister.log(jwt.getSubject(), deviceId, device.name, device.type); + DeviceRegisteredEvent.log(jwt.getSubject(), deviceId, device.name, device.type); return Response.created(URI.create(".")).build(); } catch (ConstraintViolationException e) { throw new ClientErrorException(Response.Status.CONFLICT, e); @@ -151,7 +151,7 @@ public Response remove(@PathParam("deviceId") @ValidId String deviceId) { var maybeDevice = Device.findByIdOptional(deviceId); if (maybeDevice.isPresent() && currentUser.equals(maybeDevice.get().owner)) { maybeDevice.get().delete(); - AuditEventDeviceRemove.log(jwt.getSubject(), deviceId); + DeviceRemovedEvent.log(jwt.getSubject(), deviceId); return Response.status(Response.Status.NO_CONTENT).build(); } else { return Response.status(Response.Status.NOT_FOUND).build(); 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 50f1f807f..a92037497 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java @@ -5,7 +5,6 @@ import jakarta.inject.Inject; import jakarta.transaction.Transactional; import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; @@ -18,7 +17,7 @@ import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.AccessToken; import org.cryptomator.hub.entities.AccessTokenRepository; -import org.cryptomator.hub.entities.AuditEventVaultAccessGrant; +import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent; import org.cryptomator.hub.entities.Device; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.Vault; @@ -94,7 +93,7 @@ public Response updateMyAccessTokens(@NotNull Map tokens) { } token.setVaultKey(entry.getValue()); accessTokenRepo.persist(token); - AuditEventVaultAccessGrant.log(user.id, vault.id, user.id); + VaultAccessGrantedEvent.log(user.id, vault.id, user.id); } return Response.ok().build(); } 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 42082da87..5940cd8f1 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -32,14 +32,14 @@ import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.AccessToken; import org.cryptomator.hub.entities.AccessTokenRepository; -import org.cryptomator.hub.entities.AuditEventVaultAccessGrant; -import org.cryptomator.hub.entities.AuditEventVaultCreate; -import org.cryptomator.hub.entities.AuditEventVaultKeyRetrieve; -import org.cryptomator.hub.entities.AuditEventVaultMemberAdd; -import org.cryptomator.hub.entities.AuditEventVaultMemberRemove; -import org.cryptomator.hub.entities.AuditEventVaultMemberUpdate; -import org.cryptomator.hub.entities.AuditEventVaultOwnershipClaim; -import org.cryptomator.hub.entities.AuditEventVaultUpdate; +import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent; +import org.cryptomator.hub.entities.events.VaultCreatedEvent; +import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; +import org.cryptomator.hub.entities.events.VaultMemberAddedEvent; +import org.cryptomator.hub.entities.events.VaultMemberRemovedEvent; +import org.cryptomator.hub.entities.events.VaultMemberUpdatedEvent; +import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEvent; +import org.cryptomator.hub.entities.events.VaultUpdatedEvent; import org.cryptomator.hub.entities.Authority; import org.cryptomator.hub.entities.EffectiveGroupMembership; import org.cryptomator.hub.entities.EffectiveVaultAccess; @@ -203,7 +203,7 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role var access = existingAccess.get(); access.role = role; access.persist(); - AuditEventVaultMemberUpdate.log(jwt.getSubject(), vault.id, authority.id, role); + VaultMemberUpdatedEvent.log(jwt.getSubject(), vault.id, authority.id, role); return Response.ok().build(); } else { var access = new VaultAccess(); @@ -211,7 +211,7 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role access.authority = authority; access.role = role; access.persist(); - AuditEventVaultMemberAdd.log(jwt.getSubject(), vault.id, authority.id, role); + VaultMemberAddedEvent.log(jwt.getSubject(), vault.id, authority.id, role); return Response.created(URI.create(".")).build(); } } @@ -227,7 +227,7 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role @APIResponse(responseCode = "403", description = "not a vault owner") public Response removeAuthority(@PathParam("vaultId") UUID vaultId, @PathParam("authorityId") @ValidId String authorityId) { if (VaultAccess.deleteById(new VaultAccess.Id(vaultId, authorityId))) { - AuditEventVaultMemberRemove.log(jwt.getSubject(), vaultId, authorityId); + VaultMemberRemovedEvent.log(jwt.getSubject(), vaultId, authorityId); return Response.status(Response.Status.NO_CONTENT).build(); } else { throw new NotFoundException(); @@ -272,12 +272,12 @@ public Response legacyUnlock(@PathParam("vaultId") UUID vaultId, @PathParam("dev var access = LegacyAccessToken.unlock(vaultId, deviceId, jwt.getSubject()); if (access != null) { - AuditEventVaultKeyRetrieve.log(jwt.getSubject(), vaultId, AuditEventVaultKeyRetrieve.Result.SUCCESS); + VaultKeyRetrievedEvent.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); var subscriptionStateHeaderName = "Hub-Subscription-State"; var subscriptionStateHeaderValue = license.isSet() ? "ACTIVE" : "INACTIVE"; // license expiration is not checked here, because it is checked in the ActiveLicense filter return Response.ok(access.jwe).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); } else { - AuditEventVaultKeyRetrieve.log(jwt.getSubject(), vaultId, AuditEventVaultKeyRetrieve.Result.UNAUTHORIZED); + VaultKeyRetrievedEvent.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); throw new ForbiddenException("Access to this device not granted."); } } @@ -314,14 +314,14 @@ public Response unlock(@PathParam("vaultId") UUID vaultId, @QueryParam("evenIfAr var access = accessTokenRepo.unlock(vaultId, jwt.getSubject()); if (access != null) { - AuditEventVaultKeyRetrieve.log(jwt.getSubject(), vaultId, AuditEventVaultKeyRetrieve.Result.SUCCESS); + VaultKeyRetrievedEvent.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); var subscriptionStateHeaderName = "Hub-Subscription-State"; var subscriptionStateHeaderValue = license.isSet() ? "ACTIVE" : "INACTIVE"; // license expiration is not checked here, because it is checked in the ActiveLicense filter return Response.ok(access.getVaultKey()).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); } else if (Vault.findById(vaultId) == null) { throw new NotFoundException("No such vault."); } else { - AuditEventVaultKeyRetrieve.log(jwt.getSubject(), vaultId, AuditEventVaultKeyRetrieve.Result.UNAUTHORIZED); + VaultKeyRetrievedEvent.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); throw new ForbiddenException("Access to this vault not granted."); } } @@ -362,7 +362,7 @@ public Response grantAccess(@PathParam("vaultId") UUID vaultId, @NotEmpty Map findAllInPeriod(Instant startDate, Instant endDate, long paginationId, boolean ascending, int pageSize) { - var parameters = Parameters.with("startDate", startDate).and("endDate", endDate).and("paginationId", paginationId); - - final PanacheQuery query; - if (ascending) { - query = find("#AuditEvent.listAllInPeriodAfterId", parameters); - } else { - query = find("#AuditEvent.listAllInPeriodBeforeId", parameters); - } - query.page(0, pageSize); - return query.stream(); - } - } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEventRepository.java new file mode 100644 index 000000000..4960fe4a2 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEventRepository.java @@ -0,0 +1,29 @@ +package org.cryptomator.hub.entities.events; + +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 java.time.Instant; +import java.util.stream.Stream; + +@ApplicationScoped +public class AuditEventRepository implements PanacheRepository { + + public Stream findAllInPeriod(Instant startDate, Instant endDate, long paginationId, boolean ascending, int pageSize) { + var parameters = Parameters.with("startDate", startDate).and("endDate", endDate).and("paginationId", paginationId); + + final PanacheQuery query; + if (ascending) { + query = find("#AuditEvent.listAllInPeriodAfterId", parameters); + } else { + query = find("#AuditEvent.listAllInPeriodBeforeId", parameters); + } + query.page(0, pageSize); + return query.stream(); + } + + + +} diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventDeviceRegister.java b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEvent.java similarity index 78% rename from backend/src/main/java/org/cryptomator/hub/entities/AuditEventDeviceRegister.java rename to backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEvent.java index 119e37683..5d2a5471a 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventDeviceRegister.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEvent.java @@ -1,4 +1,4 @@ -package org.cryptomator.hub.entities; +package org.cryptomator.hub.entities.events; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; @@ -6,35 +6,36 @@ import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Table; +import org.cryptomator.hub.entities.Device; import java.time.Instant; import java.util.Objects; @Entity @Table(name = "audit_event_device_register") -@DiscriminatorValue(AuditEventDeviceRegister.TYPE) -public class AuditEventDeviceRegister extends AuditEvent { +@DiscriminatorValue(DeviceRegisteredEvent.TYPE) +public class DeviceRegisteredEvent extends AuditEvent { public static final String TYPE = "DEVICE_REGISTER"; @Column(name = "registered_by") - public String registeredBy; + String registeredBy; @Column(name = "device_id") - public String deviceId; + String deviceId; @Column(name = "device_name") - public String deviceName; + String deviceName; @Column(name = "device_type") @Enumerated(EnumType.STRING) - public Device.Type deviceType; + Device.Type deviceType; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AuditEventDeviceRegister that = (AuditEventDeviceRegister) o; + DeviceRegisteredEvent that = (DeviceRegisteredEvent) o; return super.equals(that) // && Objects.equals(registeredBy, that.registeredBy) // && Objects.equals(deviceId, that.deviceId) // @@ -48,7 +49,7 @@ public int hashCode() { } public static void log(String registeredBy, String deviceId, String deviceName, Device.Type deviceType) { - var event = new AuditEventDeviceRegister(); + var event = new DeviceRegisteredEvent(); event.timestamp = Instant.now(); event.registeredBy = registeredBy; event.deviceId = deviceId; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventDeviceRemove.java b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEvent.java similarity index 79% rename from backend/src/main/java/org/cryptomator/hub/entities/AuditEventDeviceRemove.java rename to backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEvent.java index d2ac92acf..b93f6edb8 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventDeviceRemove.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEvent.java @@ -1,4 +1,4 @@ -package org.cryptomator.hub.entities; +package org.cryptomator.hub.entities.events; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; @@ -10,8 +10,8 @@ @Entity @Table(name = "audit_event_device_remove") -@DiscriminatorValue(AuditEventDeviceRemove.TYPE) -public class AuditEventDeviceRemove extends AuditEvent { +@DiscriminatorValue(DeviceRemovedEvent.TYPE) +public class DeviceRemovedEvent extends AuditEvent { public static final String TYPE = "DEVICE_REMOVE"; @@ -25,7 +25,7 @@ public class AuditEventDeviceRemove extends AuditEvent { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AuditEventDeviceRemove that = (AuditEventDeviceRemove) o; + DeviceRemovedEvent that = (DeviceRemovedEvent) o; return super.equals(that) // && Objects.equals(removedBy, that.removedBy) // && Objects.equals(deviceId, that.deviceId); @@ -37,7 +37,7 @@ public int hashCode() { } public static void log(String removedBy, String deviceId) { - var event = new AuditEventDeviceRemove(); + var event = new DeviceRemovedEvent(); event.timestamp = Instant.now(); event.removedBy = removedBy; event.deviceId = deviceId; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultAccessGrant.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultAccessGrantedEvent.java similarity index 81% rename from backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultAccessGrant.java rename to backend/src/main/java/org/cryptomator/hub/entities/events/VaultAccessGrantedEvent.java index f1ad2a242..de838d4bc 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultAccessGrant.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultAccessGrantedEvent.java @@ -1,4 +1,4 @@ -package org.cryptomator.hub.entities; +package org.cryptomator.hub.entities.events; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; @@ -11,8 +11,8 @@ @Entity @Table(name = "audit_event_vault_access_grant") -@DiscriminatorValue(AuditEventVaultAccessGrant.TYPE) -public class AuditEventVaultAccessGrant extends AuditEvent { +@DiscriminatorValue(VaultAccessGrantedEvent.TYPE) +public class VaultAccessGrantedEvent extends AuditEvent { public static final String TYPE = "VAULT_ACCESS_GRANT"; @@ -29,7 +29,7 @@ public class AuditEventVaultAccessGrant extends AuditEvent { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AuditEventVaultAccessGrant that = (AuditEventVaultAccessGrant) o; + VaultAccessGrantedEvent that = (VaultAccessGrantedEvent) o; return super.equals(that) // && Objects.equals(grantedBy, that.grantedBy) // && Objects.equals(vaultId, that.vaultId) // @@ -42,7 +42,7 @@ public int hashCode() { } public static void log(String grantedBy, UUID vaultId, String authorityId) { - var event = new AuditEventVaultAccessGrant(); + var event = new VaultAccessGrantedEvent(); event.timestamp = Instant.now(); event.grantedBy = grantedBy; event.vaultId = vaultId; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultCreate.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEvent.java similarity index 84% rename from backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultCreate.java rename to backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEvent.java index a0c60a88b..617d8cc73 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultCreate.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEvent.java @@ -1,4 +1,4 @@ -package org.cryptomator.hub.entities; +package org.cryptomator.hub.entities.events; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; @@ -11,8 +11,8 @@ @Entity @Table(name = "audit_event_vault_create") -@DiscriminatorValue(AuditEventVaultCreate.TYPE) -public class AuditEventVaultCreate extends AuditEvent { +@DiscriminatorValue(VaultCreatedEvent.TYPE) +public class VaultCreatedEvent extends AuditEvent { public static final String TYPE = "VAULT_CREATE"; @@ -32,7 +32,7 @@ public class AuditEventVaultCreate extends AuditEvent { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AuditEventVaultCreate that = (AuditEventVaultCreate) o; + VaultCreatedEvent that = (VaultCreatedEvent) o; return super.equals(that) // && Objects.equals(createdBy, that.createdBy) // && Objects.equals(vaultId, that.vaultId) // @@ -46,7 +46,7 @@ public int hashCode() { } public static void log(String createdBy, UUID vaultId, String vaultName, String vaultDescription) { - var event = new AuditEventVaultCreate(); + var event = new VaultCreatedEvent(); event.timestamp = Instant.now(); event.createdBy = createdBy; event.vaultId = vaultId; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultKeyRetrieve.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEvent.java similarity index 82% rename from backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultKeyRetrieve.java rename to backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEvent.java index a1daf8add..97f8bd29c 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultKeyRetrieve.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEvent.java @@ -1,4 +1,4 @@ -package org.cryptomator.hub.entities; +package org.cryptomator.hub.entities.events; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; @@ -13,8 +13,9 @@ @Entity @Table(name = "audit_event_vault_key_retrieve") -@DiscriminatorValue(AuditEventVaultKeyRetrieve.TYPE) -public class AuditEventVaultKeyRetrieve extends AuditEvent { +@DiscriminatorValue(VaultKeyRetrievedEvent.TYPE) +//TODO: bad naming +public class VaultKeyRetrievedEvent extends AuditEvent { public static final String TYPE = "VAULT_KEY_RETRIEVE"; @@ -32,7 +33,7 @@ public class AuditEventVaultKeyRetrieve extends AuditEvent { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AuditEventVaultKeyRetrieve that = (AuditEventVaultKeyRetrieve) o; + VaultKeyRetrievedEvent that = (VaultKeyRetrievedEvent) o; return super.equals(that) // && Objects.equals(retrievedBy, that.retrievedBy) // && Objects.equals(vaultId, that.vaultId) // @@ -45,7 +46,7 @@ public int hashCode() { } public static void log(String retrievedBy, UUID vaultId, Result result) { - var event = new AuditEventVaultKeyRetrieve(); + var event = new VaultKeyRetrievedEvent(); event.timestamp = Instant.now(); event.retrievedBy = retrievedBy; event.vaultId = vaultId; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultMemberAdd.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEvent.java similarity index 82% rename from backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultMemberAdd.java rename to backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEvent.java index 9a8b23b06..ba7dd2d70 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultMemberAdd.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEvent.java @@ -1,4 +1,4 @@ -package org.cryptomator.hub.entities; +package org.cryptomator.hub.entities.events; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; @@ -6,6 +6,7 @@ import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Table; +import org.cryptomator.hub.entities.VaultAccess; import java.time.Instant; import java.util.Objects; @@ -13,8 +14,8 @@ @Entity @Table(name = "audit_event_vault_member_add") -@DiscriminatorValue(AuditEventVaultMemberAdd.TYPE) -public class AuditEventVaultMemberAdd extends AuditEvent { +@DiscriminatorValue(VaultMemberAddedEvent.TYPE) +public class VaultMemberAddedEvent extends AuditEvent { public static final String TYPE = "VAULT_MEMBER_ADD"; @@ -35,7 +36,7 @@ public class AuditEventVaultMemberAdd extends AuditEvent { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AuditEventVaultMemberAdd that = (AuditEventVaultMemberAdd) o; + VaultMemberAddedEvent that = (VaultMemberAddedEvent) o; return super.equals(that) // && Objects.equals(addedBy, that.addedBy) // && Objects.equals(vaultId, that.vaultId) // @@ -49,7 +50,7 @@ public int hashCode() { } public static void log(String addedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { - var event = new AuditEventVaultMemberAdd(); + var event = new VaultMemberAddedEvent(); event.timestamp = Instant.now(); event.addedBy = addedBy; event.vaultId = vaultId; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultMemberRemove.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEvent.java similarity index 81% rename from backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultMemberRemove.java rename to backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEvent.java index e947d155c..f17572d1f 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultMemberRemove.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEvent.java @@ -1,4 +1,4 @@ -package org.cryptomator.hub.entities; +package org.cryptomator.hub.entities.events; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; @@ -11,8 +11,8 @@ @Entity @Table(name = "audit_event_vault_member_remove") -@DiscriminatorValue(AuditEventVaultMemberRemove.TYPE) -public class AuditEventVaultMemberRemove extends AuditEvent { +@DiscriminatorValue(VaultMemberRemovedEvent.TYPE) +public class VaultMemberRemovedEvent extends AuditEvent { public static final String TYPE = "VAULT_MEMBER_REMOVE"; @@ -29,7 +29,7 @@ public class AuditEventVaultMemberRemove extends AuditEvent { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AuditEventVaultMemberRemove that = (AuditEventVaultMemberRemove) o; + VaultMemberRemovedEvent that = (VaultMemberRemovedEvent) o; return super.equals(that) // && Objects.equals(removedBy, that.removedBy) // && Objects.equals(vaultId, that.vaultId) // @@ -42,7 +42,7 @@ public int hashCode() { } public static void log(String removedBy, UUID vaultId, String authorityId) { - var event = new AuditEventVaultMemberRemove(); + var event = new VaultMemberRemovedEvent(); event.timestamp = Instant.now(); event.removedBy = removedBy; event.vaultId = vaultId; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultMemberUpdate.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEvent.java similarity index 82% rename from backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultMemberUpdate.java rename to backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEvent.java index cc0f29883..4b364d12d 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultMemberUpdate.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEvent.java @@ -1,4 +1,4 @@ -package org.cryptomator.hub.entities; +package org.cryptomator.hub.entities.events; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; @@ -6,6 +6,7 @@ import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Table; +import org.cryptomator.hub.entities.VaultAccess; import java.time.Instant; import java.util.Objects; @@ -13,8 +14,8 @@ @Entity @Table(name = "audit_event_vault_member_update") -@DiscriminatorValue(AuditEventVaultMemberUpdate.TYPE) -public class AuditEventVaultMemberUpdate extends AuditEvent { +@DiscriminatorValue(VaultMemberUpdatedEvent.TYPE) +public class VaultMemberUpdatedEvent extends AuditEvent { public static final String TYPE = "VAULT_MEMBER_UPDATE"; @@ -35,7 +36,7 @@ public class AuditEventVaultMemberUpdate extends AuditEvent { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AuditEventVaultMemberUpdate that = (AuditEventVaultMemberUpdate) o; + VaultMemberUpdatedEvent that = (VaultMemberUpdatedEvent) o; return super.equals(that) // && Objects.equals(updatedBy, that.updatedBy) // && Objects.equals(vaultId, that.vaultId) // @@ -49,7 +50,7 @@ public int hashCode() { } public static void log(String updatedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { - var event = new AuditEventVaultMemberUpdate(); + var event = new VaultMemberUpdatedEvent(); event.timestamp = Instant.now(); event.updatedBy = updatedBy; event.vaultId = vaultId; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultOwnershipClaim.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEvent.java similarity index 77% rename from backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultOwnershipClaim.java rename to backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEvent.java index ee5fd1072..42833dd2f 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultOwnershipClaim.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEvent.java @@ -1,4 +1,4 @@ -package org.cryptomator.hub.entities; +package org.cryptomator.hub.entities.events; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; @@ -11,8 +11,8 @@ @Entity @Table(name = "audit_event_vault_ownership_claim") -@DiscriminatorValue(AuditEventVaultOwnershipClaim.TYPE) -public class AuditEventVaultOwnershipClaim extends AuditEvent { +@DiscriminatorValue(VaultOwnershipClaimedEvent.TYPE) +public class VaultOwnershipClaimedEvent extends AuditEvent { public static final String TYPE = "VAULT_OWNERSHIP_CLAIM"; @@ -26,7 +26,7 @@ public class AuditEventVaultOwnershipClaim extends AuditEvent { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AuditEventVaultOwnershipClaim that = (AuditEventVaultOwnershipClaim) o; + VaultOwnershipClaimedEvent that = (VaultOwnershipClaimedEvent) o; return super.equals(that) // && Objects.equals(claimedBy, that.claimedBy) // && Objects.equals(vaultId, that.vaultId); @@ -38,7 +38,7 @@ public int hashCode() { } public static void log(String claimedBy, UUID vaultId) { - var event = new AuditEventVaultOwnershipClaim(); + var event = new VaultOwnershipClaimedEvent(); event.timestamp = Instant.now(); event.claimedBy = claimedBy; event.vaultId = vaultId; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultUpdate.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEvent.java similarity index 86% rename from backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultUpdate.java rename to backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEvent.java index a2f857912..b62815ce0 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AuditEventVaultUpdate.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEvent.java @@ -1,4 +1,4 @@ -package org.cryptomator.hub.entities; +package org.cryptomator.hub.entities.events; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; @@ -11,8 +11,8 @@ @Entity @Table(name = "audit_event_vault_update") -@DiscriminatorValue(AuditEventVaultUpdate.TYPE) -public class AuditEventVaultUpdate extends AuditEvent { +@DiscriminatorValue(VaultUpdatedEvent.TYPE) +public class VaultUpdatedEvent extends AuditEvent { public static final String TYPE = "VAULT_UPDATE"; @@ -35,7 +35,7 @@ public class AuditEventVaultUpdate extends AuditEvent { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AuditEventVaultUpdate that = (AuditEventVaultUpdate) o; + VaultUpdatedEvent that = (VaultUpdatedEvent) o; return super.equals(that) // && Objects.equals(updatedBy, that.updatedBy) // && Objects.equals(vaultId, that.vaultId) // @@ -50,7 +50,7 @@ public int hashCode() { } public static void log(String updatedBy, UUID vaultId, String vaultName, String vaultDescription, boolean vaultArchived) { - var event = new AuditEventVaultUpdate(); + var event = new VaultUpdatedEvent(); event.timestamp = Instant.now(); event.updatedBy = updatedBy; event.vaultId = vaultId; From c9b63285c6a6e9a6fc84cc55ce861532c107213b Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 14:42:09 +0100 Subject: [PATCH 15/54] migrate DeviceRegisteredEvent to repository pattern --- .../cryptomator/hub/api/AuditLogResource.java | 2 +- .../cryptomator/hub/api/DeviceResource.java | 5 ++- .../events/DeviceRegisteredEvent.java | 42 ++++++++++++++----- .../DeviceRegisteredEventRepository.java | 21 ++++++++++ 4 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEventRepository.java 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 d1ceed896..6bb323805 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -100,7 +100,7 @@ public interface AuditEventDto { static AuditEventDto fromEntity(AuditEvent entity) { return switch (entity) { - case DeviceRegisteredEvent evt -> new DeviceRegisteredEventDto(evt.getId(), evt.getTimestamp(), DeviceRegisteredEvent.TYPE, evt.registeredBy, evt.deviceId, evt.deviceName, evt.deviceType); + case DeviceRegisteredEvent evt -> new DeviceRegisteredEventDto(evt.getId(), evt.getTimestamp(), DeviceRegisteredEvent.TYPE, evt.getRegisteredBy(), evt.getDeviceId(), evt.getDeviceName(), evt.getDeviceType()); case DeviceRemovedEvent evt -> new DeviceRemovedEventDto(evt.getId(), evt.getTimestamp(), DeviceRemovedEvent.TYPE, evt.removedBy, evt.deviceId); case VaultCreatedEvent evt -> new VaultCreatedEventDto(evt.getId(), evt.getTimestamp(), VaultCreatedEvent.TYPE, evt.createdBy, evt.vaultId, evt.vaultName, evt.vaultDescription); case VaultUpdatedEvent evt -> new VaultUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultUpdatedEvent.TYPE, evt.updatedBy, evt.vaultId, evt.vaultName, evt.vaultDescription, evt.vaultArchived); 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 7739c6890..4dffc5252 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java @@ -21,6 +21,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.events.DeviceRegisteredEvent; +import org.cryptomator.hub.entities.events.DeviceRegisteredEventRepository; import org.cryptomator.hub.entities.events.DeviceRemovedEvent; import org.cryptomator.hub.entities.Device; import org.cryptomator.hub.entities.LegacyAccessToken; @@ -50,6 +51,8 @@ public class DeviceResource { private static final Logger LOG = Logger.getLogger(DeviceResource.class); + @Inject + DeviceRegisteredEventRepository deviceRegisteredEventRepo; @Inject JsonWebToken jwt; @@ -95,7 +98,7 @@ public Response createOrUpdate(@Valid @NotNull DeviceDto dto, @PathParam("device try { device.persistAndFlush(); - DeviceRegisteredEvent.log(jwt.getSubject(), deviceId, device.name, device.type); + deviceRegisteredEventRepo.log(jwt.getSubject(), deviceId, device.name, device.type); return Response.created(URI.create(".")).build(); } catch (ConstraintViolationException e) { throw new ClientErrorException(Response.Status.CONFLICT, e); 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 5d2a5471a..fae708b7c 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 @@ -31,6 +31,38 @@ public class DeviceRegisteredEvent extends AuditEvent { @Enumerated(EnumType.STRING) Device.Type deviceType; + public String getRegisteredBy() { + return registeredBy; + } + + public void setRegisteredBy(String registeredBy) { + this.registeredBy = registeredBy; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public Device.Type getDeviceType() { + return deviceType; + } + + public void setDeviceType(Device.Type deviceType) { + this.deviceType = deviceType; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -48,14 +80,4 @@ public int hashCode() { return Objects.hash(id, registeredBy, deviceId, deviceName, deviceType); } - public static void log(String registeredBy, String deviceId, String deviceName, Device.Type deviceType) { - var event = new DeviceRegisteredEvent(); - event.timestamp = Instant.now(); - event.registeredBy = registeredBy; - event.deviceId = deviceId; - event.deviceName = deviceName; - event.deviceType = deviceType; - event.persist(); - } - } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEventRepository.java new file mode 100644 index 000000000..a81559c5f --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEventRepository.java @@ -0,0 +1,21 @@ +package org.cryptomator.hub.entities.events; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; +import org.cryptomator.hub.entities.Device; + +import java.time.Instant; + +@ApplicationScoped +public class DeviceRegisteredEventRepository implements PanacheRepository { + + public void log(String registeredBy, String deviceId, String deviceName, Device.Type deviceType) { + var event = new DeviceRegisteredEvent(); + event.setTimestamp(Instant.now()); + event.setRegisteredBy(registeredBy); + event.setDeviceId(deviceId); + event.setDeviceName(deviceName); + event.setDeviceType(deviceType); + persist(event); + } +} From b04aa2144424bee5ade68a27f9feaa4294376dd6 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 14:47:16 +0100 Subject: [PATCH 16/54] migrate DeviceRemovedEvent to repository pattern --- .../cryptomator/hub/api/AuditLogResource.java | 2 +- .../cryptomator/hub/api/DeviceResource.java | 5 +++- .../entities/events/DeviceRemovedEvent.java | 28 ++++++++++++------- .../events/DeviceRemovedEventRepository.java | 19 +++++++++++++ 4 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEventRepository.java 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 6bb323805..c501129b5 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -101,7 +101,7 @@ public interface AuditEventDto { static AuditEventDto fromEntity(AuditEvent entity) { return switch (entity) { case DeviceRegisteredEvent evt -> new DeviceRegisteredEventDto(evt.getId(), evt.getTimestamp(), DeviceRegisteredEvent.TYPE, evt.getRegisteredBy(), evt.getDeviceId(), evt.getDeviceName(), evt.getDeviceType()); - case DeviceRemovedEvent evt -> new DeviceRemovedEventDto(evt.getId(), evt.getTimestamp(), DeviceRemovedEvent.TYPE, evt.removedBy, evt.deviceId); + case DeviceRemovedEvent evt -> new DeviceRemovedEventDto(evt.getId(), evt.getTimestamp(), DeviceRemovedEvent.TYPE, evt.getRemovedBy(), evt.getDeviceId()); case VaultCreatedEvent evt -> new VaultCreatedEventDto(evt.getId(), evt.getTimestamp(), VaultCreatedEvent.TYPE, evt.createdBy, evt.vaultId, evt.vaultName, evt.vaultDescription); case VaultUpdatedEvent evt -> new VaultUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultUpdatedEvent.TYPE, evt.updatedBy, evt.vaultId, evt.vaultName, evt.vaultDescription, evt.vaultArchived); case VaultAccessGrantedEvent evt -> new VaultAccessGrantedEventDto(evt.getId(), evt.getTimestamp(), VaultAccessGrantedEvent.TYPE, evt.grantedBy, evt.vaultId, evt.authorityId); 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 4dffc5252..1eb81c2c8 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java @@ -27,6 +27,7 @@ import org.cryptomator.hub.entities.LegacyAccessToken; import org.cryptomator.hub.entities.LegacyDevice; import org.cryptomator.hub.entities.User; +import org.cryptomator.hub.entities.events.DeviceRemovedEventRepository; import org.cryptomator.hub.validation.NoHtmlOrScriptChars; import org.cryptomator.hub.validation.OnlyBase64Chars; import org.cryptomator.hub.validation.ValidId; @@ -54,6 +55,8 @@ public class DeviceResource { @Inject DeviceRegisteredEventRepository deviceRegisteredEventRepo; @Inject + DeviceRemovedEventRepository deviceRemovedEventRepo; + @Inject JsonWebToken jwt; @GET @@ -154,7 +157,7 @@ public Response remove(@PathParam("deviceId") @ValidId String deviceId) { var maybeDevice = Device.findByIdOptional(deviceId); if (maybeDevice.isPresent() && currentUser.equals(maybeDevice.get().owner)) { maybeDevice.get().delete(); - DeviceRemovedEvent.log(jwt.getSubject(), deviceId); + deviceRemovedEventRepo.log(jwt.getSubject(), deviceId); return Response.status(Response.Status.NO_CONTENT).build(); } else { return Response.status(Response.Status.NOT_FOUND).build(); 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 b93f6edb8..2bcd6f21d 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 @@ -16,10 +16,26 @@ public class DeviceRemovedEvent extends AuditEvent { public static final String TYPE = "DEVICE_REMOVE"; @Column(name = "removed_by") - public String removedBy; + String removedBy; @Column(name = "device_id") - public String deviceId; + String deviceId; + + public String getRemovedBy() { + return removedBy; + } + + public void setRemovedBy(String removedBy) { + this.removedBy = removedBy; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } @Override public boolean equals(Object o) { @@ -36,12 +52,4 @@ public int hashCode() { return Objects.hash(id, removedBy, deviceId); } - public static void log(String removedBy, String deviceId) { - var event = new DeviceRemovedEvent(); - event.timestamp = Instant.now(); - event.removedBy = removedBy; - event.deviceId = deviceId; - event.persist(); - } - } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEventRepository.java new file mode 100644 index 000000000..7b17edc53 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEventRepository.java @@ -0,0 +1,19 @@ +package org.cryptomator.hub.entities.events; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +import java.time.Instant; + +@ApplicationScoped +public class DeviceRemovedEventRepository implements PanacheRepository { + + public void log(String removedBy, String deviceId) { + var event = new DeviceRemovedEvent(); + event.setTimestamp(Instant.now()); + event.setRemovedBy(removedBy); + event.setDeviceId(deviceId); + persist(event); + } + +} From e57e77d7ec4e97b5be4b8bbe7449d70d3b1ad124 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 14:57:27 +0100 Subject: [PATCH 17/54] migrate VaultAccessGrantedEvent to repository pattern --- .../cryptomator/hub/api/AuditLogResource.java | 2 +- .../cryptomator/hub/api/UsersResource.java | 5 ++- .../cryptomator/hub/api/VaultResource.java | 5 ++- .../events/VaultAccessGrantedEvent.java | 39 +++++++++++++------ .../VaultAccessGrantedEventRepository.java | 21 ++++++++++ 5 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultAccessGrantedEventRepository.java 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 c501129b5..1aaaede96 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -104,7 +104,7 @@ static AuditEventDto fromEntity(AuditEvent entity) { case DeviceRemovedEvent evt -> new DeviceRemovedEventDto(evt.getId(), evt.getTimestamp(), DeviceRemovedEvent.TYPE, evt.getRemovedBy(), evt.getDeviceId()); case VaultCreatedEvent evt -> new VaultCreatedEventDto(evt.getId(), evt.getTimestamp(), VaultCreatedEvent.TYPE, evt.createdBy, evt.vaultId, evt.vaultName, evt.vaultDescription); case VaultUpdatedEvent evt -> new VaultUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultUpdatedEvent.TYPE, evt.updatedBy, evt.vaultId, evt.vaultName, evt.vaultDescription, evt.vaultArchived); - case VaultAccessGrantedEvent evt -> new VaultAccessGrantedEventDto(evt.getId(), evt.getTimestamp(), VaultAccessGrantedEvent.TYPE, evt.grantedBy, evt.vaultId, evt.authorityId); + case VaultAccessGrantedEvent evt -> new VaultAccessGrantedEventDto(evt.getId(), evt.getTimestamp(), VaultAccessGrantedEvent.TYPE, evt.getGrantedBy(), evt.getVaultId(), evt.getAuthorityId()); case VaultKeyRetrievedEvent evt -> new VaultKeyRetrievedEventDto(evt.getId(), evt.getTimestamp(), VaultKeyRetrievedEvent.TYPE, evt.retrievedBy, evt.vaultId, evt.result); case VaultMemberAddedEvent evt -> new VaultMemberAddedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberAddedEvent.TYPE, evt.addedBy, evt.vaultId, evt.authorityId, evt.role); case VaultMemberRemovedEvent evt -> new VaultMemberRemovedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberRemovedEvent.TYPE, evt.removedBy, evt.vaultId, evt.authorityId); 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 a92037497..b2c619976 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java @@ -21,6 +21,7 @@ import org.cryptomator.hub.entities.Device; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.Vault; +import org.cryptomator.hub.entities.events.VaultAccessGrantedEventRepository; import org.eclipse.microprofile.jwt.JsonWebToken; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; @@ -41,6 +42,8 @@ public class UsersResource { @Inject AccessTokenRepository accessTokenRepo; + @Inject + VaultAccessGrantedEventRepository vaultAccessGrantedEventRepo; @Inject JsonWebToken jwt; @@ -93,7 +96,7 @@ public Response updateMyAccessTokens(@NotNull Map tokens) { } token.setVaultKey(entry.getValue()); accessTokenRepo.persist(token); - VaultAccessGrantedEvent.log(user.id, vault.id, user.id); + vaultAccessGrantedEventRepo.log(user.id, vault.id, user.id); } return Response.ok().build(); } 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 5940cd8f1..664f1b4b4 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -33,6 +33,7 @@ import org.cryptomator.hub.entities.AccessToken; import org.cryptomator.hub.entities.AccessTokenRepository; import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent; +import org.cryptomator.hub.entities.events.VaultAccessGrantedEventRepository; import org.cryptomator.hub.entities.events.VaultCreatedEvent; import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; import org.cryptomator.hub.entities.events.VaultMemberAddedEvent; @@ -75,6 +76,8 @@ public class VaultResource { @Inject AccessTokenRepository accessTokenRepo; + @Inject + VaultAccessGrantedEventRepository vaultAccessGrantedEventRepo; @Inject JsonWebToken jwt; @@ -362,7 +365,7 @@ public Response grantAccess(@PathParam("vaultId") UUID vaultId, @NotEmpty Map { + + public void log(String grantedBy, UUID vaultId, String authorityId) { + var event = new VaultAccessGrantedEvent(); + event.setTimestamp(Instant.now()); + event.setGrantedBy(grantedBy); + event.setVaultId(vaultId); + event.setAuthorityId(authorityId); + persist(event); + } + +} From 5c3a1a9ada7b3042ca081ab98a814eb2420efdc4 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 15:03:50 +0100 Subject: [PATCH 18/54] migrate VaultCreatedEvent to repository pattern --- .../cryptomator/hub/api/AuditLogResource.java | 2 +- .../cryptomator/hub/api/VaultResource.java | 6 ++- .../entities/events/VaultCreatedEvent.java | 50 +++++++++++++------ .../events/VaultCreatedEventRepository.java | 22 ++++++++ 4 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEventRepository.java 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 1aaaede96..9770eba79 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -102,7 +102,7 @@ static AuditEventDto fromEntity(AuditEvent entity) { return switch (entity) { case DeviceRegisteredEvent evt -> new DeviceRegisteredEventDto(evt.getId(), evt.getTimestamp(), DeviceRegisteredEvent.TYPE, evt.getRegisteredBy(), evt.getDeviceId(), evt.getDeviceName(), evt.getDeviceType()); case DeviceRemovedEvent evt -> new DeviceRemovedEventDto(evt.getId(), evt.getTimestamp(), DeviceRemovedEvent.TYPE, evt.getRemovedBy(), evt.getDeviceId()); - case VaultCreatedEvent evt -> new VaultCreatedEventDto(evt.getId(), evt.getTimestamp(), VaultCreatedEvent.TYPE, evt.createdBy, evt.vaultId, evt.vaultName, evt.vaultDescription); + case VaultCreatedEvent evt -> new VaultCreatedEventDto(evt.getId(), evt.getTimestamp(), VaultCreatedEvent.TYPE, evt.getCreatedBy(), evt.getVaultId(), evt.getVaultName(), evt.getVaultDescription()); case VaultUpdatedEvent evt -> new VaultUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultUpdatedEvent.TYPE, evt.updatedBy, evt.vaultId, evt.vaultName, evt.vaultDescription, evt.vaultArchived); case VaultAccessGrantedEvent evt -> new VaultAccessGrantedEventDto(evt.getId(), evt.getTimestamp(), VaultAccessGrantedEvent.TYPE, evt.getGrantedBy(), evt.getVaultId(), evt.getAuthorityId()); case VaultKeyRetrievedEvent evt -> new VaultKeyRetrievedEventDto(evt.getId(), evt.getTimestamp(), VaultKeyRetrievedEvent.TYPE, evt.retrievedBy, evt.vaultId, evt.result); 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 664f1b4b4..b32d04968 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -35,6 +35,7 @@ import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent; import org.cryptomator.hub.entities.events.VaultAccessGrantedEventRepository; import org.cryptomator.hub.entities.events.VaultCreatedEvent; +import org.cryptomator.hub.entities.events.VaultCreatedEventRepository; import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; import org.cryptomator.hub.entities.events.VaultMemberAddedEvent; import org.cryptomator.hub.entities.events.VaultMemberRemovedEvent; @@ -78,6 +79,8 @@ public class VaultResource { AccessTokenRepository accessTokenRepo; @Inject VaultAccessGrantedEventRepository vaultAccessGrantedEventRepo; + @Inject + VaultCreatedEventRepository vaultCreatedEventRepo; @Inject JsonWebToken jwt; @@ -424,12 +427,13 @@ public Response createOrUpdate(@PathParam("vaultId") UUID vaultId, @Valid @NotNu vault.persistAndFlush(); // trigger PersistenceException before we continue with if (existingVault.isEmpty()) { + vaultCreatedEventRepo.log(currentUser.id, vault.id, vault.name, vault.description); + var access = new VaultAccess(); access.vault = vault; access.authority = currentUser; access.role = VaultAccess.Role.OWNER; access.persist(); - VaultCreatedEvent.log(currentUser.id, vault.id, vault.name, vault.description); VaultMemberAddedEvent.log(currentUser.id, vaultId, currentUser.id, VaultAccess.Role.OWNER); return Response.created(URI.create(".")).contentLocation(URI.create(".")).entity(VaultDto.fromEntity(vault)).type(MediaType.APPLICATION_JSON).build(); } else { 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 617d8cc73..f95aa7cad 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 @@ -17,16 +17,48 @@ public class VaultCreatedEvent extends AuditEvent { public static final String TYPE = "VAULT_CREATE"; @Column(name = "created_by") - public String createdBy; + String createdBy; @Column(name = "vault_id") - public UUID vaultId; + UUID vaultId; @Column(name = "vault_name") - public String vaultName; + String vaultName; @Column(name = "vault_description") - public String vaultDescription; + String vaultDescription; + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public UUID getVaultId() { + return vaultId; + } + + public void setVaultId(UUID vaultId) { + this.vaultId = vaultId; + } + + public String getVaultName() { + return vaultName; + } + + public void setVaultName(String vaultName) { + this.vaultName = vaultName; + } + + public String getVaultDescription() { + return vaultDescription; + } + + public void setVaultDescription(String vaultDescription) { + this.vaultDescription = vaultDescription; + } @Override public boolean equals(Object o) { @@ -45,14 +77,4 @@ public int hashCode() { return Objects.hash(id, createdBy, vaultId, vaultName, vaultDescription); } - public static void log(String createdBy, UUID vaultId, String vaultName, String vaultDescription) { - var event = new VaultCreatedEvent(); - event.timestamp = Instant.now(); - event.createdBy = createdBy; - event.vaultId = vaultId; - event.vaultName = vaultName; - event.vaultDescription = vaultDescription; - event.persist(); - } - } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEventRepository.java new file mode 100644 index 000000000..6acfb8607 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEventRepository.java @@ -0,0 +1,22 @@ +package org.cryptomator.hub.entities.events; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +import java.time.Instant; +import java.util.UUID; + +@ApplicationScoped +public class VaultCreatedEventRepository implements PanacheRepository { + + public void log(String createdBy, UUID vaultId, String vaultName, String vaultDescription) { + var event = new VaultCreatedEvent(); + event.setTimestamp(Instant.now()); + event.setCreatedBy(createdBy); + event.setVaultId(vaultId); + event.setVaultName(vaultName); + event.setVaultDescription(vaultDescription); + persist(event); + } + +} From 3e6bf87ae75a28f34d9eb8b71a12467dce29edce Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 15:22:44 +0100 Subject: [PATCH 19/54] migrate VaultKeyRetrievedEvent to repository pattern --- .../cryptomator/hub/api/AuditLogResource.java | 2 +- .../cryptomator/hub/api/VaultResource.java | 11 +++-- .../events/VaultKeyRetrievedEvent.java | 40 +++++++++++++------ .../VaultKeyRetrievedEventRepository.java | 20 ++++++++++ 4 files changed, 55 insertions(+), 18 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEventRepository.java 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 9770eba79..b25a2abe2 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -105,7 +105,7 @@ static AuditEventDto fromEntity(AuditEvent entity) { case VaultCreatedEvent evt -> new VaultCreatedEventDto(evt.getId(), evt.getTimestamp(), VaultCreatedEvent.TYPE, evt.getCreatedBy(), evt.getVaultId(), evt.getVaultName(), evt.getVaultDescription()); case VaultUpdatedEvent evt -> new VaultUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultUpdatedEvent.TYPE, evt.updatedBy, evt.vaultId, evt.vaultName, evt.vaultDescription, evt.vaultArchived); case VaultAccessGrantedEvent evt -> new VaultAccessGrantedEventDto(evt.getId(), evt.getTimestamp(), VaultAccessGrantedEvent.TYPE, evt.getGrantedBy(), evt.getVaultId(), evt.getAuthorityId()); - case VaultKeyRetrievedEvent evt -> new VaultKeyRetrievedEventDto(evt.getId(), evt.getTimestamp(), VaultKeyRetrievedEvent.TYPE, evt.retrievedBy, evt.vaultId, evt.result); + case VaultKeyRetrievedEvent evt -> new VaultKeyRetrievedEventDto(evt.getId(), evt.getTimestamp(), VaultKeyRetrievedEvent.TYPE, evt.getRetrievedBy(), evt.getVaultId(), evt.getResult()); case VaultMemberAddedEvent evt -> new VaultMemberAddedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberAddedEvent.TYPE, evt.addedBy, evt.vaultId, evt.authorityId, evt.role); case VaultMemberRemovedEvent evt -> new VaultMemberRemovedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberRemovedEvent.TYPE, evt.removedBy, evt.vaultId, evt.authorityId); case VaultMemberUpdatedEvent evt -> new VaultMemberUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberUpdatedEvent.TYPE, evt.updatedBy, evt.vaultId, evt.authorityId, evt.role); 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 b32d04968..33a92a0e5 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -37,6 +37,7 @@ import org.cryptomator.hub.entities.events.VaultCreatedEvent; import org.cryptomator.hub.entities.events.VaultCreatedEventRepository; import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; +import org.cryptomator.hub.entities.events.VaultKeyRetrievedEventRepository; import org.cryptomator.hub.entities.events.VaultMemberAddedEvent; import org.cryptomator.hub.entities.events.VaultMemberRemovedEvent; import org.cryptomator.hub.entities.events.VaultMemberUpdatedEvent; @@ -81,6 +82,8 @@ public class VaultResource { VaultAccessGrantedEventRepository vaultAccessGrantedEventRepo; @Inject VaultCreatedEventRepository vaultCreatedEventRepo; + @Inject + VaultKeyRetrievedEventRepository vaultKeyRetrievedEventRepo; @Inject JsonWebToken jwt; @@ -278,12 +281,12 @@ public Response legacyUnlock(@PathParam("vaultId") UUID vaultId, @PathParam("dev var access = LegacyAccessToken.unlock(vaultId, deviceId, jwt.getSubject()); if (access != null) { - VaultKeyRetrievedEvent.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); + vaultKeyRetrievedEventRepo.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); var subscriptionStateHeaderName = "Hub-Subscription-State"; var subscriptionStateHeaderValue = license.isSet() ? "ACTIVE" : "INACTIVE"; // license expiration is not checked here, because it is checked in the ActiveLicense filter return Response.ok(access.jwe).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); } else { - VaultKeyRetrievedEvent.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); + vaultKeyRetrievedEventRepo.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); throw new ForbiddenException("Access to this device not granted."); } } @@ -320,14 +323,14 @@ public Response unlock(@PathParam("vaultId") UUID vaultId, @QueryParam("evenIfAr var access = accessTokenRepo.unlock(vaultId, jwt.getSubject()); if (access != null) { - VaultKeyRetrievedEvent.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); + vaultKeyRetrievedEventRepo.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); var subscriptionStateHeaderName = "Hub-Subscription-State"; var subscriptionStateHeaderValue = license.isSet() ? "ACTIVE" : "INACTIVE"; // license expiration is not checked here, because it is checked in the ActiveLicense filter return Response.ok(access.getVaultKey()).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); } else if (Vault.findById(vaultId) == null) { throw new NotFoundException("No such vault."); } else { - VaultKeyRetrievedEvent.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); + vaultKeyRetrievedEventRepo.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); throw new ForbiddenException("Access to this vault not granted."); } } 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 97f8bd29c..7219505e5 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 @@ -14,20 +14,43 @@ @Entity @Table(name = "audit_event_vault_key_retrieve") @DiscriminatorValue(VaultKeyRetrievedEvent.TYPE) -//TODO: bad naming public class VaultKeyRetrievedEvent extends AuditEvent { public static final String TYPE = "VAULT_KEY_RETRIEVE"; @Column(name = "retrieved_by") - public String retrievedBy; + String retrievedBy; @Column(name = "vault_id") - public UUID vaultId; + UUID vaultId; @Column(name = "result") @Enumerated(EnumType.STRING) - public Result result; + Result result; + + public String getRetrievedBy() { + return retrievedBy; + } + + public void setRetrievedBy(String retrievedBy) { + this.retrievedBy = retrievedBy; + } + + public UUID getVaultId() { + return vaultId; + } + + public void setVaultId(UUID vaultId) { + this.vaultId = vaultId; + } + + public Result getResult() { + return result; + } + + public void setResult(Result result) { + this.result = result; + } @Override public boolean equals(Object o) { @@ -45,15 +68,6 @@ public int hashCode() { return Objects.hash(id, retrievedBy, vaultId, result); } - public static void log(String retrievedBy, UUID vaultId, Result result) { - var event = new VaultKeyRetrievedEvent(); - event.timestamp = Instant.now(); - event.retrievedBy = retrievedBy; - event.vaultId = vaultId; - event.result = result; - event.persist(); - } - public enum Result { SUCCESS, UNAUTHORIZED diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEventRepository.java new file mode 100644 index 000000000..fc4f1391e --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEventRepository.java @@ -0,0 +1,20 @@ +package org.cryptomator.hub.entities.events; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +import java.time.Instant; +import java.util.UUID; + +@ApplicationScoped +public class VaultKeyRetrievedEventRepository implements PanacheRepository { + + public void log(String retrievedBy, UUID vaultId, VaultKeyRetrievedEvent.Result result) { + var event = new VaultKeyRetrievedEvent(); + event.setTimestamp(Instant.now()); + event.setRetrievedBy(retrievedBy); + event.setVaultId(vaultId); + event.setResult(result); + persist(event); + } +} From 0b4198d7589fe417f9a41823655227d0655a5183 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 15:26:39 +0100 Subject: [PATCH 20/54] migrate VaultMemberAddedEvent to repository pattern --- .../cryptomator/hub/api/AuditLogResource.java | 2 +- .../cryptomator/hub/api/VaultResource.java | 9 ++-- .../events/VaultMemberAddedEvent.java | 50 +++++++++++++------ .../VaultMemberAddedEventRepository.java | 23 +++++++++ 4 files changed, 66 insertions(+), 18 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEventRepository.java 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 b25a2abe2..d670d61b4 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -106,7 +106,7 @@ static AuditEventDto fromEntity(AuditEvent entity) { case VaultUpdatedEvent evt -> new VaultUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultUpdatedEvent.TYPE, evt.updatedBy, evt.vaultId, evt.vaultName, evt.vaultDescription, evt.vaultArchived); case VaultAccessGrantedEvent evt -> new VaultAccessGrantedEventDto(evt.getId(), evt.getTimestamp(), VaultAccessGrantedEvent.TYPE, evt.getGrantedBy(), evt.getVaultId(), evt.getAuthorityId()); case VaultKeyRetrievedEvent evt -> new VaultKeyRetrievedEventDto(evt.getId(), evt.getTimestamp(), VaultKeyRetrievedEvent.TYPE, evt.getRetrievedBy(), evt.getVaultId(), evt.getResult()); - case VaultMemberAddedEvent evt -> new VaultMemberAddedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberAddedEvent.TYPE, evt.addedBy, evt.vaultId, evt.authorityId, evt.role); + case VaultMemberAddedEvent evt -> new VaultMemberAddedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberAddedEvent.TYPE, evt.getAddedBy(), evt.getVaultId(), evt.getAuthorityId(), evt.getRole()); case VaultMemberRemovedEvent evt -> new VaultMemberRemovedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberRemovedEvent.TYPE, evt.removedBy, evt.vaultId, evt.authorityId); case VaultMemberUpdatedEvent evt -> new VaultMemberUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberUpdatedEvent.TYPE, evt.updatedBy, evt.vaultId, evt.authorityId, evt.role); case VaultOwnershipClaimedEvent evt -> new VaultOwnershipClaimedEventDto(evt.getId(), evt.getTimestamp(), VaultOwnershipClaimedEvent.TYPE, evt.claimedBy, evt.vaultId); 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 33a92a0e5..5e5be13dc 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -39,6 +39,7 @@ import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; import org.cryptomator.hub.entities.events.VaultKeyRetrievedEventRepository; import org.cryptomator.hub.entities.events.VaultMemberAddedEvent; +import org.cryptomator.hub.entities.events.VaultMemberAddedEventRepository; import org.cryptomator.hub.entities.events.VaultMemberRemovedEvent; import org.cryptomator.hub.entities.events.VaultMemberUpdatedEvent; import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEvent; @@ -84,6 +85,8 @@ public class VaultResource { VaultCreatedEventRepository vaultCreatedEventRepo; @Inject VaultKeyRetrievedEventRepository vaultKeyRetrievedEventRepo; + @Inject + VaultMemberAddedEventRepository vaultMemberAddedEventRepo; @Inject JsonWebToken jwt; @@ -220,7 +223,7 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role access.authority = authority; access.role = role; access.persist(); - VaultMemberAddedEvent.log(jwt.getSubject(), vault.id, authority.id, role); + vaultMemberAddedEventRepo.log(jwt.getSubject(), vault.id, authority.id, role); return Response.created(URI.create(".")).build(); } } @@ -437,7 +440,7 @@ public Response createOrUpdate(@PathParam("vaultId") UUID vaultId, @Valid @NotNu access.authority = currentUser; access.role = VaultAccess.Role.OWNER; access.persist(); - VaultMemberAddedEvent.log(currentUser.id, vaultId, currentUser.id, VaultAccess.Role.OWNER); + vaultMemberAddedEventRepo.log(currentUser.id, vaultId, currentUser.id, VaultAccess.Role.OWNER); return Response.created(URI.create(".")).contentLocation(URI.create(".")).entity(VaultDto.fromEntity(vault)).type(MediaType.APPLICATION_JSON).build(); } else { VaultUpdatedEvent.log(currentUser.id, vault.id, vault.name, vault.description, vault.archived); @@ -488,7 +491,7 @@ public Response claimOwnership(@PathParam("vaultId") UUID vaultId, @FormParam("p access.authority = currentUser; access.role = VaultAccess.Role.OWNER; access.persist(); - VaultMemberAddedEvent.log(currentUser.id, vaultId, currentUser.id, VaultAccess.Role.OWNER); + vaultMemberAddedEventRepo.log(currentUser.id, vaultId, currentUser.id, VaultAccess.Role.OWNER); } vault.salt = null; 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 ba7dd2d70..f7a3a94dd 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 @@ -20,17 +20,49 @@ public class VaultMemberAddedEvent extends AuditEvent { public static final String TYPE = "VAULT_MEMBER_ADD"; @Column(name = "added_by") - public String addedBy; + String addedBy; @Column(name = "vault_id") - public UUID vaultId; + UUID vaultId; @Column(name = "authority_id") - public String authorityId; + String authorityId; @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) - public VaultAccess.Role role; + VaultAccess.Role role; + + public String getAddedBy() { + return addedBy; + } + + public void setAddedBy(String addedBy) { + this.addedBy = addedBy; + } + + public UUID getVaultId() { + return vaultId; + } + + public void setVaultId(UUID vaultId) { + this.vaultId = vaultId; + } + + public String getAuthorityId() { + return authorityId; + } + + public void setAuthorityId(String authorityId) { + this.authorityId = authorityId; + } + + public VaultAccess.Role getRole() { + return role; + } + + public void setRole(VaultAccess.Role role) { + this.role = role; + } @Override public boolean equals(Object o) { @@ -49,14 +81,4 @@ public int hashCode() { return Objects.hash(id, addedBy, vaultId, authorityId, role); } - public static void log(String addedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { - var event = new VaultMemberAddedEvent(); - event.timestamp = Instant.now(); - event.addedBy = addedBy; - event.vaultId = vaultId; - event.authorityId = authorityId; - event.role = role; - event.persist(); - } - } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEventRepository.java new file mode 100644 index 000000000..3ec350015 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEventRepository.java @@ -0,0 +1,23 @@ +package org.cryptomator.hub.entities.events; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; +import org.cryptomator.hub.entities.VaultAccess; + +import java.time.Instant; +import java.util.UUID; + +@ApplicationScoped +public class VaultMemberAddedEventRepository implements PanacheRepository { + + + public void log(String addedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { + var event = new VaultMemberAddedEvent(); + event.setTimestamp(Instant.now()); + event.setAddedBy(addedBy); + event.setVaultId(vaultId); + event.setAuthorityId(authorityId); + event.setRole(role); + persist(event); + } +} From 5982fcf6992e47b2d5e8610e2e944e9ad0c8489e Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 15:31:38 +0100 Subject: [PATCH 21/54] migrate VaultMemberRemovedEvent to repository pattern --- .../cryptomator/hub/api/AuditLogResource.java | 2 +- .../cryptomator/hub/api/VaultResource.java | 5 ++- .../events/VaultMemberRemovedEvent.java | 39 +++++++++++++------ .../VaultMemberRemovedEventRepository.java | 21 ++++++++++ 4 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEventRepository.java 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 d670d61b4..e611eb439 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -107,7 +107,7 @@ static AuditEventDto fromEntity(AuditEvent entity) { case VaultAccessGrantedEvent evt -> new VaultAccessGrantedEventDto(evt.getId(), evt.getTimestamp(), VaultAccessGrantedEvent.TYPE, evt.getGrantedBy(), evt.getVaultId(), evt.getAuthorityId()); case VaultKeyRetrievedEvent evt -> new VaultKeyRetrievedEventDto(evt.getId(), evt.getTimestamp(), VaultKeyRetrievedEvent.TYPE, evt.getRetrievedBy(), evt.getVaultId(), evt.getResult()); case VaultMemberAddedEvent evt -> new VaultMemberAddedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberAddedEvent.TYPE, evt.getAddedBy(), evt.getVaultId(), evt.getAuthorityId(), evt.getRole()); - case VaultMemberRemovedEvent evt -> new VaultMemberRemovedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberRemovedEvent.TYPE, evt.removedBy, evt.vaultId, evt.authorityId); + case VaultMemberRemovedEvent evt -> new VaultMemberRemovedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberRemovedEvent.TYPE, evt.getRemovedBy(), evt.getVaultId(), evt.getAuthorityId()); case VaultMemberUpdatedEvent evt -> new VaultMemberUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberUpdatedEvent.TYPE, evt.updatedBy, evt.vaultId, evt.authorityId, evt.role); case VaultOwnershipClaimedEvent evt -> new VaultOwnershipClaimedEventDto(evt.getId(), evt.getTimestamp(), VaultOwnershipClaimedEvent.TYPE, evt.claimedBy, evt.vaultId); default -> throw new UnsupportedOperationException("conversion not implemented for event type " + entity.getClass()); 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 5e5be13dc..e1ed2f031 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -41,6 +41,7 @@ import org.cryptomator.hub.entities.events.VaultMemberAddedEvent; import org.cryptomator.hub.entities.events.VaultMemberAddedEventRepository; import org.cryptomator.hub.entities.events.VaultMemberRemovedEvent; +import org.cryptomator.hub.entities.events.VaultMemberRemovedEventRepository; import org.cryptomator.hub.entities.events.VaultMemberUpdatedEvent; import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEvent; import org.cryptomator.hub.entities.events.VaultUpdatedEvent; @@ -87,6 +88,8 @@ public class VaultResource { VaultKeyRetrievedEventRepository vaultKeyRetrievedEventRepo; @Inject VaultMemberAddedEventRepository vaultMemberAddedEventRepo; + @Inject + VaultMemberRemovedEventRepository vaultMemberRemovedEventRepo; @Inject JsonWebToken jwt; @@ -239,7 +242,7 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role @APIResponse(responseCode = "403", description = "not a vault owner") public Response removeAuthority(@PathParam("vaultId") UUID vaultId, @PathParam("authorityId") @ValidId String authorityId) { if (VaultAccess.deleteById(new VaultAccess.Id(vaultId, authorityId))) { - VaultMemberRemovedEvent.log(jwt.getSubject(), vaultId, authorityId); + vaultMemberRemovedEventRepo.log(jwt.getSubject(), vaultId, authorityId); return Response.status(Response.Status.NO_CONTENT).build(); } else { throw new NotFoundException(); 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 f17572d1f..27285fcf9 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 @@ -17,13 +17,37 @@ public class VaultMemberRemovedEvent extends AuditEvent { public static final String TYPE = "VAULT_MEMBER_REMOVE"; @Column(name = "removed_by") - public String removedBy; + String removedBy; @Column(name = "vault_id") - public UUID vaultId; + UUID vaultId; @Column(name = "authority_id") - public String authorityId; + String authorityId; + + public String getRemovedBy() { + return removedBy; + } + + public void setRemovedBy(String removedBy) { + this.removedBy = removedBy; + } + + public UUID getVaultId() { + return vaultId; + } + + public void setVaultId(UUID vaultId) { + this.vaultId = vaultId; + } + + public String getAuthorityId() { + return authorityId; + } + + public void setAuthorityId(String authorityId) { + this.authorityId = authorityId; + } @Override public boolean equals(Object o) { @@ -41,13 +65,4 @@ public int hashCode() { return Objects.hash(id, removedBy, vaultId, authorityId); } - public static void log(String removedBy, UUID vaultId, String authorityId) { - var event = new VaultMemberRemovedEvent(); - event.timestamp = Instant.now(); - event.removedBy = removedBy; - event.vaultId = vaultId; - event.authorityId = authorityId; - event.persist(); - } - } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEventRepository.java new file mode 100644 index 000000000..0e5813150 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEventRepository.java @@ -0,0 +1,21 @@ +package org.cryptomator.hub.entities.events; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +import java.time.Instant; +import java.util.UUID; + +@ApplicationScoped +public class VaultMemberRemovedEventRepository implements PanacheRepository { + + public void log(String removedBy, UUID vaultId, String authorityId) { + var event = new VaultMemberRemovedEvent(); + event.setTimestamp(Instant.now()); + event.setRemovedBy(removedBy); + event.setVaultId(vaultId); + event.setAuthorityId(authorityId); + persist(event); + } + +} From db12bd936b869a381b314a33c837244a32498e31 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 15:39:07 +0100 Subject: [PATCH 22/54] migrate VaultMemberUpdatedEvent to repository pattern --- .../cryptomator/hub/api/AuditLogResource.java | 2 +- .../cryptomator/hub/api/VaultResource.java | 7 ++- .../events/VaultMemberUpdatedEvent.java | 50 +++++++++++++------ .../VaultMemberUpdatedEventRepository.java | 24 +++++++++ 4 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEventRepository.java 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 e611eb439..de06c86c0 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -108,7 +108,7 @@ static AuditEventDto fromEntity(AuditEvent entity) { case VaultKeyRetrievedEvent evt -> new VaultKeyRetrievedEventDto(evt.getId(), evt.getTimestamp(), VaultKeyRetrievedEvent.TYPE, evt.getRetrievedBy(), evt.getVaultId(), evt.getResult()); case VaultMemberAddedEvent evt -> new VaultMemberAddedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberAddedEvent.TYPE, evt.getAddedBy(), evt.getVaultId(), evt.getAuthorityId(), evt.getRole()); case VaultMemberRemovedEvent evt -> new VaultMemberRemovedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberRemovedEvent.TYPE, evt.getRemovedBy(), evt.getVaultId(), evt.getAuthorityId()); - case VaultMemberUpdatedEvent evt -> new VaultMemberUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberUpdatedEvent.TYPE, evt.updatedBy, evt.vaultId, evt.authorityId, evt.role); + case VaultMemberUpdatedEvent evt -> new VaultMemberUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberUpdatedEvent.TYPE, evt.getUpdatedBy(), evt.getVaultId(), evt.getAuthorityId(), evt.getRole()); case VaultOwnershipClaimedEvent evt -> new VaultOwnershipClaimedEventDto(evt.getId(), evt.getTimestamp(), VaultOwnershipClaimedEvent.TYPE, evt.claimedBy, evt.vaultId); default -> throw new UnsupportedOperationException("conversion not implemented for event type " + entity.getClass()); }; 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 e1ed2f031..f320c0fd8 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -43,6 +43,7 @@ import org.cryptomator.hub.entities.events.VaultMemberRemovedEvent; import org.cryptomator.hub.entities.events.VaultMemberRemovedEventRepository; import org.cryptomator.hub.entities.events.VaultMemberUpdatedEvent; +import org.cryptomator.hub.entities.events.VaultMemberUpdatedEventRepository; import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEvent; import org.cryptomator.hub.entities.events.VaultUpdatedEvent; import org.cryptomator.hub.entities.Authority; @@ -90,6 +91,8 @@ public class VaultResource { VaultMemberAddedEventRepository vaultMemberAddedEventRepo; @Inject VaultMemberRemovedEventRepository vaultMemberRemovedEventRepo; + @Inject + VaultMemberUpdatedEventRepository vaultMemberUpdatedEventRepo; @Inject JsonWebToken jwt; @@ -218,7 +221,7 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role var access = existingAccess.get(); access.role = role; access.persist(); - VaultMemberUpdatedEvent.log(jwt.getSubject(), vault.id, authority.id, role); + vaultMemberUpdatedEventRepo.log(jwt.getSubject(), vault.id, authority.id, role); return Response.ok().build(); } else { var access = new VaultAccess(); @@ -487,7 +490,7 @@ public Response claimOwnership(@PathParam("vaultId") UUID vaultId, @FormParam("p var access = existingAccess.get(); access.role = VaultAccess.Role.OWNER; access.persist(); - VaultMemberUpdatedEvent.log(currentUser.id, vaultId, currentUser.id, VaultAccess.Role.OWNER); + vaultMemberUpdatedEventRepo.log(currentUser.id, vaultId, currentUser.id, VaultAccess.Role.OWNER); } else { var access = new VaultAccess(); access.vault = vault; 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 4b364d12d..dce832957 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 @@ -20,17 +20,49 @@ public class VaultMemberUpdatedEvent extends AuditEvent { public static final String TYPE = "VAULT_MEMBER_UPDATE"; @Column(name = "updated_by") - public String updatedBy; + String updatedBy; @Column(name = "vault_id") - public UUID vaultId; + UUID vaultId; @Column(name = "authority_id") - public String authorityId; + String authorityId; @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) - public VaultAccess.Role role; + VaultAccess.Role role; + + public String getUpdatedBy() { + return updatedBy; + } + + public void setUpdatedBy(String updatedBy) { + this.updatedBy = updatedBy; + } + + public UUID getVaultId() { + return vaultId; + } + + public void setVaultId(UUID vaultId) { + this.vaultId = vaultId; + } + + public String getAuthorityId() { + return authorityId; + } + + public void setAuthorityId(String authorityId) { + this.authorityId = authorityId; + } + + public VaultAccess.Role getRole() { + return role; + } + + public void setRole(VaultAccess.Role role) { + this.role = role; + } @Override public boolean equals(Object o) { @@ -49,14 +81,4 @@ public int hashCode() { return Objects.hash(id, updatedBy, vaultId, authorityId, role); } - public static void log(String updatedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { - var event = new VaultMemberUpdatedEvent(); - event.timestamp = Instant.now(); - event.updatedBy = updatedBy; - event.vaultId = vaultId; - event.authorityId = authorityId; - event.role = role; - event.persist(); - } - } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEventRepository.java new file mode 100644 index 000000000..e67c036e6 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEventRepository.java @@ -0,0 +1,24 @@ +package org.cryptomator.hub.entities.events; + +import jakarta.enterprise.context.ApplicationScoped; +import org.cryptomator.hub.entities.VaultAccess; + +import java.time.Instant; +import java.util.UUID; + +import static io.quarkus.hibernate.orm.panache.PanacheEntityBase.persist; + +@ApplicationScoped +public class VaultMemberUpdatedEventRepository { + + public void log(String updatedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { + var event = new VaultMemberUpdatedEvent(); + event.setTimestamp(Instant.now()); + event.setUpdatedBy(updatedBy); + event.setVaultId(vaultId); + event.setAuthorityId(authorityId); + event.setRole(role); + persist(event); + } + +} From 2526f8cb4962f5a4580acf8f62a2dae1616b1300 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 15:49:15 +0100 Subject: [PATCH 23/54] migrate VaultOwnershipClaimed to repository pattern --- .../cryptomator/hub/api/AuditLogResource.java | 2 +- .../cryptomator/hub/api/VaultResource.java | 5 +++- .../events/VaultOwnershipClaimedEvent.java | 28 ++++++++++++------- .../VaultOwnershipClaimedEventRepository.java | 20 +++++++++++++ 4 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEventRepository.java 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 de06c86c0..0acc0a821 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -109,7 +109,7 @@ static AuditEventDto fromEntity(AuditEvent entity) { case VaultMemberAddedEvent evt -> new VaultMemberAddedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberAddedEvent.TYPE, evt.getAddedBy(), evt.getVaultId(), evt.getAuthorityId(), evt.getRole()); case VaultMemberRemovedEvent evt -> new VaultMemberRemovedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberRemovedEvent.TYPE, evt.getRemovedBy(), evt.getVaultId(), evt.getAuthorityId()); case VaultMemberUpdatedEvent evt -> new VaultMemberUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberUpdatedEvent.TYPE, evt.getUpdatedBy(), evt.getVaultId(), evt.getAuthorityId(), evt.getRole()); - case VaultOwnershipClaimedEvent evt -> new VaultOwnershipClaimedEventDto(evt.getId(), evt.getTimestamp(), VaultOwnershipClaimedEvent.TYPE, evt.claimedBy, evt.vaultId); + case VaultOwnershipClaimedEvent evt -> new VaultOwnershipClaimedEventDto(evt.getId(), evt.getTimestamp(), VaultOwnershipClaimedEvent.TYPE, evt.getClaimedBy(), evt.getVaultId()); default -> throw new UnsupportedOperationException("conversion not implemented for event type " + entity.getClass()); }; } 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 f320c0fd8..b33d24bfa 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -45,6 +45,7 @@ import org.cryptomator.hub.entities.events.VaultMemberUpdatedEvent; import org.cryptomator.hub.entities.events.VaultMemberUpdatedEventRepository; import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEvent; +import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEventRepository; import org.cryptomator.hub.entities.events.VaultUpdatedEvent; import org.cryptomator.hub.entities.Authority; import org.cryptomator.hub.entities.EffectiveGroupMembership; @@ -93,6 +94,8 @@ public class VaultResource { VaultMemberRemovedEventRepository vaultMemberRemovedEventRepo; @Inject VaultMemberUpdatedEventRepository vaultMemberUpdatedEventRepo; + @Inject + VaultOwnershipClaimedEventRepository vaultOwnershipClaimedEventRepo; @Inject JsonWebToken jwt; @@ -507,7 +510,7 @@ public Response claimOwnership(@PathParam("vaultId") UUID vaultId, @FormParam("p vault.authenticationPublicKey = null; vault.persist(); - VaultOwnershipClaimedEvent.log(currentUser.id, vaultId); + vaultOwnershipClaimedEventRepo.log(currentUser.id, vaultId); return Response.ok(VaultDto.fromEntity(vault), MediaType.APPLICATION_JSON).build(); } 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 42833dd2f..c9de69769 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 @@ -17,10 +17,26 @@ public class VaultOwnershipClaimedEvent extends AuditEvent { public static final String TYPE = "VAULT_OWNERSHIP_CLAIM"; @Column(name = "claimed_by") - public String claimedBy; + String claimedBy; @Column(name = "vault_id") - public UUID vaultId; + UUID vaultId; + + public String getClaimedBy() { + return claimedBy; + } + + public void setClaimedBy(String claimedBy) { + this.claimedBy = claimedBy; + } + + public UUID getVaultId() { + return vaultId; + } + + public void setVaultId(UUID vaultId) { + this.vaultId = vaultId; + } @Override public boolean equals(Object o) { @@ -37,12 +53,4 @@ public int hashCode() { return Objects.hash(id, claimedBy, vaultId); } - public static void log(String claimedBy, UUID vaultId) { - var event = new VaultOwnershipClaimedEvent(); - event.timestamp = Instant.now(); - event.claimedBy = claimedBy; - event.vaultId = vaultId; - event.persist(); - } - } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEventRepository.java new file mode 100644 index 000000000..276eb6baa --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEventRepository.java @@ -0,0 +1,20 @@ +package org.cryptomator.hub.entities.events; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +import java.time.Instant; +import java.util.UUID; + +@ApplicationScoped +public class VaultOwnershipClaimedEventRepository implements PanacheRepository { + + public void log(String claimedBy, UUID vaultId) { + var event = new VaultOwnershipClaimedEvent(); + event.setTimestamp(Instant.now()); + event.setClaimedBy(claimedBy); + event.setVaultId(vaultId); + persist(event); + } + +} From 7b631142ad9ac3c91f6fe1284881e977f0a4b277 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 15:55:04 +0100 Subject: [PATCH 24/54] migrate VaultUpdatedEvent to repository pattern --- .../cryptomator/hub/api/AuditLogResource.java | 2 +- .../cryptomator/hub/api/VaultResource.java | 28 ++++----- .../entities/events/VaultUpdatedEvent.java | 62 ++++++++++++++----- .../events/VaultUpdatedEventRepository.java | 23 +++++++ 4 files changed, 81 insertions(+), 34 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEventRepository.java 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 0acc0a821..b44ae6c48 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -103,7 +103,7 @@ static AuditEventDto fromEntity(AuditEvent entity) { case DeviceRegisteredEvent evt -> new DeviceRegisteredEventDto(evt.getId(), evt.getTimestamp(), DeviceRegisteredEvent.TYPE, evt.getRegisteredBy(), evt.getDeviceId(), evt.getDeviceName(), evt.getDeviceType()); case DeviceRemovedEvent evt -> new DeviceRemovedEventDto(evt.getId(), evt.getTimestamp(), DeviceRemovedEvent.TYPE, evt.getRemovedBy(), evt.getDeviceId()); case VaultCreatedEvent evt -> new VaultCreatedEventDto(evt.getId(), evt.getTimestamp(), VaultCreatedEvent.TYPE, evt.getCreatedBy(), evt.getVaultId(), evt.getVaultName(), evt.getVaultDescription()); - case VaultUpdatedEvent evt -> new VaultUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultUpdatedEvent.TYPE, evt.updatedBy, evt.vaultId, evt.vaultName, evt.vaultDescription, evt.vaultArchived); + case VaultUpdatedEvent evt -> new VaultUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultUpdatedEvent.TYPE, evt.getUpdatedBy(), evt.getVaultId(), evt.getVaultName(), evt.getVaultDescription(), evt.isVaultArchived()); case VaultAccessGrantedEvent evt -> new VaultAccessGrantedEventDto(evt.getId(), evt.getTimestamp(), VaultAccessGrantedEvent.TYPE, evt.getGrantedBy(), evt.getVaultId(), evt.getAuthorityId()); case VaultKeyRetrievedEvent evt -> new VaultKeyRetrievedEventDto(evt.getId(), evt.getTimestamp(), VaultKeyRetrievedEvent.TYPE, evt.getRetrievedBy(), evt.getVaultId(), evt.getResult()); case VaultMemberAddedEvent evt -> new VaultMemberAddedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberAddedEvent.TYPE, evt.getAddedBy(), evt.getVaultId(), evt.getAuthorityId(), evt.getRole()); 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 b33d24bfa..26d9edef3 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -32,21 +32,6 @@ import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.AccessToken; import org.cryptomator.hub.entities.AccessTokenRepository; -import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent; -import org.cryptomator.hub.entities.events.VaultAccessGrantedEventRepository; -import org.cryptomator.hub.entities.events.VaultCreatedEvent; -import org.cryptomator.hub.entities.events.VaultCreatedEventRepository; -import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; -import org.cryptomator.hub.entities.events.VaultKeyRetrievedEventRepository; -import org.cryptomator.hub.entities.events.VaultMemberAddedEvent; -import org.cryptomator.hub.entities.events.VaultMemberAddedEventRepository; -import org.cryptomator.hub.entities.events.VaultMemberRemovedEvent; -import org.cryptomator.hub.entities.events.VaultMemberRemovedEventRepository; -import org.cryptomator.hub.entities.events.VaultMemberUpdatedEvent; -import org.cryptomator.hub.entities.events.VaultMemberUpdatedEventRepository; -import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEvent; -import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEventRepository; -import org.cryptomator.hub.entities.events.VaultUpdatedEvent; import org.cryptomator.hub.entities.Authority; import org.cryptomator.hub.entities.EffectiveGroupMembership; import org.cryptomator.hub.entities.EffectiveVaultAccess; @@ -55,6 +40,15 @@ import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.VaultAccess; +import org.cryptomator.hub.entities.events.VaultAccessGrantedEventRepository; +import org.cryptomator.hub.entities.events.VaultCreatedEventRepository; +import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; +import org.cryptomator.hub.entities.events.VaultKeyRetrievedEventRepository; +import org.cryptomator.hub.entities.events.VaultMemberAddedEventRepository; +import org.cryptomator.hub.entities.events.VaultMemberRemovedEventRepository; +import org.cryptomator.hub.entities.events.VaultMemberUpdatedEventRepository; +import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEventRepository; +import org.cryptomator.hub.entities.events.VaultUpdatedEventRepository; import org.cryptomator.hub.filters.ActiveLicense; import org.cryptomator.hub.filters.VaultRole; import org.cryptomator.hub.license.LicenseHolder; @@ -96,6 +90,8 @@ public class VaultResource { VaultMemberUpdatedEventRepository vaultMemberUpdatedEventRepo; @Inject VaultOwnershipClaimedEventRepository vaultOwnershipClaimedEventRepo; + @Inject + VaultUpdatedEventRepository vaultUpdatedEventRepo; @Inject JsonWebToken jwt; @@ -452,7 +448,7 @@ public Response createOrUpdate(@PathParam("vaultId") UUID vaultId, @Valid @NotNu vaultMemberAddedEventRepo.log(currentUser.id, vaultId, currentUser.id, VaultAccess.Role.OWNER); return Response.created(URI.create(".")).contentLocation(URI.create(".")).entity(VaultDto.fromEntity(vault)).type(MediaType.APPLICATION_JSON).build(); } else { - VaultUpdatedEvent.log(currentUser.id, vault.id, vault.name, vault.description, vault.archived); + vaultUpdatedEventRepo.log(currentUser.id, vault.id, vault.name, vault.description, vault.archived); return Response.ok(VaultDto.fromEntity(vault), MediaType.APPLICATION_JSON).build(); } } 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 b62815ce0..d1e3c454a 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 @@ -5,7 +5,6 @@ import jakarta.persistence.Entity; import jakarta.persistence.Table; -import java.time.Instant; import java.util.Objects; import java.util.UUID; @@ -17,19 +16,59 @@ public class VaultUpdatedEvent extends AuditEvent { public static final String TYPE = "VAULT_UPDATE"; @Column(name = "updated_by") - public String updatedBy; + String updatedBy; @Column(name = "vault_id") - public UUID vaultId; + UUID vaultId; @Column(name = "vault_name") - public String vaultName; + String vaultName; @Column(name = "vault_description") - public String vaultDescription; + String vaultDescription; @Column(name = "vault_archived") - public boolean vaultArchived; + boolean vaultArchived; + + public String getUpdatedBy() { + return updatedBy; + } + + public void setUpdatedBy(String updatedBy) { + this.updatedBy = updatedBy; + } + + public UUID getVaultId() { + return vaultId; + } + + public void setVaultId(UUID vaultId) { + this.vaultId = vaultId; + } + + public String getVaultName() { + return vaultName; + } + + public void setVaultName(String vaultName) { + this.vaultName = vaultName; + } + + public String getVaultDescription() { + return vaultDescription; + } + + public void setVaultDescription(String vaultDescription) { + this.vaultDescription = vaultDescription; + } + + public boolean isVaultArchived() { + return vaultArchived; + } + + public void setVaultArchived(boolean vaultArchived) { + this.vaultArchived = vaultArchived; + } @Override public boolean equals(Object o) { @@ -49,15 +88,4 @@ public int hashCode() { return Objects.hash(id, updatedBy, vaultId, vaultName, vaultDescription, vaultArchived); } - public static void log(String updatedBy, UUID vaultId, String vaultName, String vaultDescription, boolean vaultArchived) { - var event = new VaultUpdatedEvent(); - event.timestamp = Instant.now(); - event.updatedBy = updatedBy; - event.vaultId = vaultId; - event.vaultName = vaultName; - event.vaultDescription = vaultDescription; - event.vaultArchived = vaultArchived; - event.persist(); - } - } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEventRepository.java new file mode 100644 index 000000000..9c80c4f6e --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEventRepository.java @@ -0,0 +1,23 @@ +package org.cryptomator.hub.entities.events; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +import java.time.Instant; +import java.util.UUID; + +@ApplicationScoped +public class VaultUpdatedEventRepository implements PanacheRepository { + + public void log(String updatedBy, UUID vaultId, String vaultName, String vaultDescription, boolean vaultArchived) { + var event = new VaultUpdatedEvent(); + event.setTimestamp(Instant.now()); + event.setUpdatedBy(updatedBy); + event.setVaultId(vaultId); + event.setVaultName(vaultName); + event.setVaultDescription(vaultDescription); + event.setVaultArchived(vaultArchived); + persist(event); + } + +} From cd28994254e0c35a26cad1273d8bd480673daed2 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 19 Mar 2024 22:40:19 +0100 Subject: [PATCH 25/54] reorder imports --- .../main/java/org/cryptomator/hub/api/AuditLogResource.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 b44ae6c48..f158e0c94 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -12,10 +12,12 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; +import org.cryptomator.hub.entities.Device; +import org.cryptomator.hub.entities.VaultAccess; import org.cryptomator.hub.entities.events.AuditEvent; +import org.cryptomator.hub.entities.events.AuditEventRepository; import org.cryptomator.hub.entities.events.DeviceRegisteredEvent; import org.cryptomator.hub.entities.events.DeviceRemovedEvent; -import org.cryptomator.hub.entities.events.AuditEventRepository; import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent; import org.cryptomator.hub.entities.events.VaultCreatedEvent; import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; @@ -24,8 +26,6 @@ import org.cryptomator.hub.entities.events.VaultMemberUpdatedEvent; import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEvent; import org.cryptomator.hub.entities.events.VaultUpdatedEvent; -import org.cryptomator.hub.entities.Device; -import org.cryptomator.hub.entities.VaultAccess; import org.cryptomator.hub.license.LicenseHolder; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn; From a3df7d99da322f72d1eeeed85eb9738e01b90c83 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 20 Mar 2024 10:37:58 +0100 Subject: [PATCH 26/54] migrate Authority, Group and User entity to new repository pattern --- .../hub/KeycloakRemoteUserProvider.java | 14 +-- .../org/cryptomator/hub/RemoteUserPuller.java | 35 +++++--- .../hub/api/AuthorityResource.java | 10 ++- .../cryptomator/hub/api/DeviceResource.java | 18 ++-- .../org/cryptomator/hub/api/GroupDto.java | 2 +- .../cryptomator/hub/api/GroupsResource.java | 14 ++- .../org/cryptomator/hub/api/MemberDto.java | 4 +- .../java/org/cryptomator/hub/api/UserDto.java | 2 +- .../cryptomator/hub/api/UsersResource.java | 50 +++++------ .../cryptomator/hub/api/VaultResource.java | 51 ++++++----- .../cryptomator/hub/entities/Authority.java | 26 +++--- .../hub/entities/AuthorityRepository.java | 21 +++++ .../entities/EffectiveGroupMembership.java | 79 ----------------- .../org/cryptomator/hub/entities/Group.java | 9 +- .../hub/entities/GroupRepository.java | 8 ++ .../org/cryptomator/hub/entities/User.java | 85 ++++++++++++++++--- .../hub/entities/UserRepository.java | 24 ++++++ .../hub/KeycloakRemoteUserProviderTest.java | 67 ++++++++------- .../cryptomator/hub/RemoteUserPullerTest.java | 34 ++++---- .../cryptomator/hub/entities/EntityIT.java | 4 +- 20 files changed, 319 insertions(+), 238 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/AuthorityRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/GroupRepository.java create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/UserRepository.java diff --git a/backend/src/main/java/org/cryptomator/hub/KeycloakRemoteUserProvider.java b/backend/src/main/java/org/cryptomator/hub/KeycloakRemoteUserProvider.java index e56be0bc2..abdc80f07 100644 --- a/backend/src/main/java/org/cryptomator/hub/KeycloakRemoteUserProvider.java +++ b/backend/src/main/java/org/cryptomator/hub/KeycloakRemoteUserProvider.java @@ -68,10 +68,10 @@ private Predicate notSyncerUser() { private User mapToUser(UserRepresentation userRepresentation) { var userEntity = new User(); - userEntity.id = userRepresentation.getId(); - userEntity.name = userRepresentation.getUsername(); - userEntity.email = userRepresentation.getEmail(); - parsePictureUrl(userRepresentation.getAttributes()).ifPresent(it -> userEntity.pictureUrl = it); + userEntity.setId(userRepresentation.getId()); + userEntity.setName(userRepresentation.getUsername()); + userEntity.setEmail(userRepresentation.getEmail()); + parsePictureUrl(userRepresentation.getAttributes()).ifPresent(userEntity::setPictureUrl); return userEntity; } @@ -96,9 +96,9 @@ List groups(RealmResource realm) { // TODO add sub groups and the members of the sub group to it too using `group.getSubGroups()` recursively var members = deepCollectMembers(realm, group.getId()); var groupEntity = new Group(); - groupEntity.id = group.getId(); - groupEntity.name = group.getName(); - groupEntity.members = members; + groupEntity.setId(group.getId()); + groupEntity.setName(group.getName()); + groupEntity.setMembers(members); return groupEntity; }).toList(); } diff --git a/backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java b/backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java index c691b6c15..7ca9922ce 100644 --- a/backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java +++ b/backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java @@ -5,8 +5,11 @@ import jakarta.inject.Inject; import jakarta.transaction.Transactional; import org.cryptomator.hub.entities.Authority; +import org.cryptomator.hub.entities.AuthorityRepository; import org.cryptomator.hub.entities.Group; +import org.cryptomator.hub.entities.GroupRepository; import org.cryptomator.hub.entities.User; +import org.cryptomator.hub.entities.UserRepository; import java.util.HashSet; import java.util.Map; @@ -17,20 +20,26 @@ @ApplicationScoped public class RemoteUserPuller { + @Inject + AuthorityRepository authorityRepo; + @Inject + GroupRepository groupRepo; + @Inject + UserRepository userRepo; @Inject RemoteUserProvider remoteUserProvider; @Scheduled(every = "{hub.keycloak.syncer-period}") void sync() { - var keycloakGroups = remoteUserProvider.groups().stream().collect(Collectors.toMap(g -> g.id, Function.identity())); - var keycloakUsers = remoteUserProvider.users().stream().collect(Collectors.toMap(u -> u.id, Function.identity())); + var keycloakGroups = remoteUserProvider.groups().stream().collect(Collectors.toMap(Authority::getId, Function.identity())); + var keycloakUsers = remoteUserProvider.users().stream().collect(Collectors.toMap(Authority::getId, Function.identity())); sync(keycloakGroups, keycloakUsers); } @Transactional void sync(Map keycloakGroups, Map keycloakUsers) { - var databaseGroups = Group.findAll().stream().collect(Collectors.toMap(g -> g.id, Function.identity())); - var databaseUsers = User.findAll().stream().collect(Collectors.toMap(u -> u.id, Function.identity())); + var databaseGroups = groupRepo.findAll().stream().collect(Collectors.toMap(Authority::getId, Function.identity())); + var databaseUsers = userRepo.findAll().stream().collect(Collectors.toMap(Authority::getId, Function.identity())); sync(keycloakGroups, keycloakUsers, databaseGroups, databaseUsers); } @@ -48,7 +57,7 @@ void sync(Map keycloakGroups, Map keycloakUsers, Ma void syncAddedAuthorities(Map keycloakAuthorities, Map databaseAuthorities) { var addedAuthority = diff(keycloakAuthorities.keySet(), databaseAuthorities.keySet()); for (var id : addedAuthority) { - keycloakAuthorities.get(id).persist(); + authorityRepo.persist(keycloakAuthorities.get(id)); } } @@ -56,7 +65,7 @@ void syncAddedAuthorities(Map keycloakAuthoriti Set syncDeletedAuthorities(Map keycloakAuthorities, Map databaseAuthorities) { var deletedAuthorities = diff(databaseAuthorities.keySet(), keycloakAuthorities.keySet()); for (var id : deletedAuthorities) { - databaseAuthorities.get(id).delete(); + authorityRepo.delete(keycloakAuthorities.get(id)); } return deletedAuthorities; } @@ -67,10 +76,10 @@ void syncUpdatedUsers(Map keycloakUsers, Map databas for (var id : updatedUsers) { var dbUser = databaseUsers.get(id); var kcUser = keycloakUsers.get(id); - dbUser.pictureUrl = kcUser.pictureUrl; - dbUser.name = kcUser.name; - dbUser.email = kcUser.email; - dbUser.persist(); + dbUser.setPictureUrl(kcUser.getPictureUrl()); + dbUser.setName(kcUser.getName()); + dbUser.setEmail(kcUser.getEmail()); + userRepo.persist(dbUser); } } @@ -81,10 +90,10 @@ void syncUpdatedGroups(Map keycloakGroups, Map dat var dbGroup = databaseGroups.get(id); var kcGroup = keycloakGroups.get(id); - dbGroup.name = kcGroup.name; + dbGroup.setName(kcGroup.getName()); - dbGroup.members.addAll(diff(kcGroup.members, dbGroup.members)); - dbGroup.members.removeAll(diff(dbGroup.members, kcGroup.members)); + dbGroup.getMembers().addAll(diff(kcGroup.getMembers(), dbGroup.getMembers())); + dbGroup.getMembers().removeAll(diff(dbGroup.getMembers(), kcGroup.getMembers())); // TODO why don't we run dbGroup.persist()? } } 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 774d3aa43..6287f4d06 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java @@ -1,13 +1,14 @@ package org.cryptomator.hub.api; import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; import jakarta.validation.constraints.NotBlank; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; -import org.cryptomator.hub.entities.Authority; +import org.cryptomator.hub.entities.AuthorityRepository; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.jboss.resteasy.reactive.NoCache; @@ -18,6 +19,9 @@ @Produces(MediaType.TEXT_PLAIN) public class AuthorityResource { + @Inject + AuthorityRepository authorityRepo; + @GET @Path("/search") @RolesAllowed("user") @@ -25,7 +29,7 @@ public class AuthorityResource { @NoCache @Operation(summary = "search authority by name") public List search(@QueryParam("query") @NotBlank String query) { - return Authority.byName(query).map(AuthorityDto::fromEntity).toList(); + return authorityRepo.byName(query).map(AuthorityDto::fromEntity).toList(); } @GET @@ -36,7 +40,7 @@ public List search(@QueryParam("query") @NotBlank String query) { @Operation(summary = "lists all authorities matching the given ids", description = "lists for each id in the list its corresponding authority. Ignores all id's where an authority cannot be found") @APIResponse(responseCode = "200") public List getSome(@QueryParam("ids") List authorityIds) { - return Authority.findAllInList(authorityIds).map(AuthorityDto::fromEntity).toList(); + return authorityRepo.findAllInList(authorityIds).map(AuthorityDto::fromEntity).toList(); } } \ No newline at end of file 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 1eb81c2c8..b90082ee0 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java @@ -20,13 +20,12 @@ import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.cryptomator.hub.entities.events.DeviceRegisteredEvent; -import org.cryptomator.hub.entities.events.DeviceRegisteredEventRepository; -import org.cryptomator.hub.entities.events.DeviceRemovedEvent; import org.cryptomator.hub.entities.Device; import org.cryptomator.hub.entities.LegacyAccessToken; import org.cryptomator.hub.entities.LegacyDevice; import org.cryptomator.hub.entities.User; +import org.cryptomator.hub.entities.UserRepository; +import org.cryptomator.hub.entities.events.DeviceRegisteredEventRepository; import org.cryptomator.hub.entities.events.DeviceRemovedEventRepository; import org.cryptomator.hub.validation.NoHtmlOrScriptChars; import org.cryptomator.hub.validation.OnlyBase64Chars; @@ -57,6 +56,8 @@ public class DeviceResource { @Inject DeviceRemovedEventRepository deviceRemovedEventRepo; @Inject + UserRepository userRepo; + @Inject JsonWebToken jwt; @GET @@ -86,7 +87,7 @@ public Response createOrUpdate(@Valid @NotNull DeviceDto dto, @PathParam("device } catch (NoResultException e) { device = new Device(); device.id = deviceId; - device.owner = User.findById(jwt.getSubject()); + device.owner = userRepo.findById(jwt.getSubject()); device.creationTime = Instant.now().truncatedTo(ChronoUnit.MILLIS); device.type = dto.type != null ? dto.type : Device.Type.DESKTOP; // default to desktop for backwards compatibility @@ -137,7 +138,7 @@ public DeviceDto get(@PathParam("deviceId") @ValidId String deviceId) { @APIResponse(responseCode = "200") public Map getLegacyAccessTokens(@PathParam("deviceId") @ValidId String deviceId) { return LegacyAccessToken.getByDeviceAndOwner(deviceId, jwt.getSubject()) - .collect(Collectors.toMap(token -> token.id.vaultId , token -> token.jwe)); + .collect(Collectors.toMap(token -> token.id.vaultId, token -> token.jwe)); } @DELETE @@ -153,7 +154,7 @@ public Response remove(@PathParam("deviceId") @ValidId String deviceId) { return Response.status(Response.Status.BAD_REQUEST).entity("deviceId cannot be empty").build(); } - User currentUser = User.findById(jwt.getSubject()); + User currentUser = userRepo.findById(jwt.getSubject()); var maybeDevice = Device.findByIdOptional(deviceId); if (maybeDevice.isPresent() && currentUser.equals(maybeDevice.get().owner)) { maybeDevice.get().delete(); @@ -173,10 +174,11 @@ public record DeviceDto(@JsonProperty("id") @ValidId String id, @JsonProperty("creationTime") Instant creationTime) { public static DeviceDto fromEntity(Device entity) { - return new DeviceDto(entity.id, entity.name, entity.type, entity.publickey, entity.userPrivateKey, entity.owner.id, entity.creationTime.truncatedTo(ChronoUnit.MILLIS)); + return new DeviceDto(entity.id, entity.name, entity.type, entity.publickey, entity.userPrivateKey, entity.owner.getId(), entity.creationTime.truncatedTo(ChronoUnit.MILLIS)); } } - public record LegacyAccessTokenDto(@JsonProperty("vaultId") UUID vaultId, @JsonProperty("token") String token) {} + public record LegacyAccessTokenDto(@JsonProperty("vaultId") UUID vaultId, @JsonProperty("token") String token) { + } } 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 3be1a4fa8..2f1620ab9 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/GroupDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/GroupDto.java @@ -10,7 +10,7 @@ public final class GroupDto extends AuthorityDto { } public static GroupDto fromEntity(Group group) { - return new GroupDto(group.id, group.name); + return new GroupDto(group.getId(), group.getName()); } } 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 32272995a..76cffee5d 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java @@ -1,13 +1,14 @@ package org.cryptomator.hub.api; import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; -import org.cryptomator.hub.entities.EffectiveGroupMembership; -import org.cryptomator.hub.entities.Group; +import org.cryptomator.hub.entities.GroupRepository; +import org.cryptomator.hub.entities.UserRepository; import org.cryptomator.hub.validation.ValidId; import org.eclipse.microprofile.openapi.annotations.Operation; @@ -16,13 +17,18 @@ @Path("/groups") public class GroupsResource { + @Inject + UserRepository userRepo; + @Inject + GroupRepository groupRepo; + @GET @Path("/") @RolesAllowed("user") @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "list all groups") public List getAll() { - return Group.findAll().stream().map(GroupDto::fromEntity).toList(); + return groupRepo.findAll().stream().map(GroupDto::fromEntity).toList(); } @GET @@ -31,7 +37,7 @@ public List getAll() { @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "list all effective group members") public List getEffectiveMembers(@PathParam("groupId") @ValidId String groupId) { - return EffectiveGroupMembership.getEffectiveGroupUsers(groupId).map(UserDto::justPublicInfo).toList(); + return userRepo.getEffectiveGroupUsers(groupId).map(UserDto::justPublicInfo).toList(); } } \ No newline at end of file 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 e8c152825..bcae87d44 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/MemberDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/MemberDto.java @@ -16,11 +16,11 @@ public final class MemberDto extends AuthorityDto { } public static MemberDto fromEntity(User user, VaultAccess.Role role) { - return new MemberDto(user.id, Type.USER, user.name, user.pictureUrl, role); + return new MemberDto(user.getId(), Type.USER, user.getName(), user.getPictureUrl(), role); } public static MemberDto fromEntity(Group group, VaultAccess.Role role) { - return new MemberDto(group.id, Type.GROUP, group.name, null, role); + return new MemberDto(group.getId(), Type.GROUP, group.getName(), null, role); } } 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 4e4f3b02b..1ed02ec7d 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UserDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UserDto.java @@ -32,6 +32,6 @@ public final class UserDto extends AuthorityDto { } public static UserDto justPublicInfo(User user) { - return new UserDto(user.id, user.name, user.pictureUrl, user.email, Set.of(), user.publicKey, null, null); + return new UserDto(user.getId(), user.getName(), user.getPictureUrl(), user.getEmail(), Set.of(), user.getPublicKey(), null, null); } } 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 b2c619976..dd1c5f952 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java @@ -17,9 +17,9 @@ import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.AccessToken; import org.cryptomator.hub.entities.AccessTokenRepository; -import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent; import org.cryptomator.hub.entities.Device; import org.cryptomator.hub.entities.User; +import org.cryptomator.hub.entities.UserRepository; import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.events.VaultAccessGrantedEventRepository; import org.eclipse.microprofile.jwt.JsonWebToken; @@ -44,6 +44,8 @@ public class UsersResource { AccessTokenRepository accessTokenRepo; @Inject VaultAccessGrantedEventRepository vaultAccessGrantedEventRepo; + @Inject + UserRepository userRepo; @Inject JsonWebToken jwt; @@ -57,20 +59,20 @@ public class UsersResource { @APIResponse(responseCode = "201", description = "user created or updated") public Response putMe(@Nullable @Valid UserDto dto) { var userId = jwt.getSubject(); - User user = User.findById(userId); + User user = userRepo.findById(userId); if (user == null) { user = new User(); - user.id = userId; + user.setId(userId); } - user.name = jwt.getName(); - user.pictureUrl = jwt.getClaim("picture"); - user.email = jwt.getClaim("email"); + user.setName(jwt.getName()); + user.setPictureUrl(jwt.getClaim("picture")); + user.setEmail(jwt.getClaim("email")); if (dto != null) { - user.publicKey = dto.publicKey; - user.privateKey = dto.privateKey; - user.setupCode = dto.setupCode; + user.setPublicKey(dto.publicKey); + user.setPrivateKey(dto.privateKey); + user.setSetupCode(dto.setupCode); } - user.persist(); + userRepo.persist(user); return Response.created(URI.create(".")).build(); } @@ -82,13 +84,13 @@ public Response putMe(@Nullable @Valid UserDto dto) { @Operation(summary = "adds/updates user-specific vault keys", description = "Stores one or more vaultid-vaultkey-tuples for the currently logged-in user, as defined in the request body ({vault1: token1, vault2: token2, ...}).") @APIResponse(responseCode = "200", description = "all keys stored") public Response updateMyAccessTokens(@NotNull Map tokens) { - var user = User.findById(jwt.getSubject()); + var user = userRepo.findById(jwt.getSubject()); for (var entry : tokens.entrySet()) { var vault = Vault.findById(entry.getKey()); if (vault == null) { continue; // skip } - var token = accessTokenRepo.findById(new AccessToken.AccessId(user.id, vault.id)); + var token = accessTokenRepo.findById(new AccessToken.AccessId(user.getId(), vault.id)); if (token == null) { token = new AccessToken(); token.setVault(vault); @@ -96,7 +98,7 @@ public Response updateMyAccessTokens(@NotNull Map tokens) { } token.setVaultKey(entry.getValue()); accessTokenRepo.persist(token); - vaultAccessGrantedEventRepo.log(user.id, vault.id, user.id); + vaultAccessGrantedEventRepo.log(user.getId(), vault.id, user.getId()); } return Response.ok().build(); } @@ -111,10 +113,10 @@ public Response updateMyAccessTokens(@NotNull Map tokens) { @APIResponse(responseCode = "200", description = "returns the current user") @APIResponse(responseCode = "404", description = "no user matching the subject of the JWT passed as Bearer Token") public UserDto getMe(@QueryParam("withDevices") boolean withDevices) { - User user = User.findById(jwt.getSubject()); - Function mapDevices = d -> new DeviceResource.DeviceDto(d.id, d.name, d.type, d.publickey, d.userPrivateKey, d.owner.id, d.creationTime.truncatedTo(ChronoUnit.MILLIS)); + User user = userRepo.findById(jwt.getSubject()); + Function mapDevices = d -> new DeviceResource.DeviceDto(d.id, d.name, d.type, d.publickey, d.userPrivateKey, d.owner.getId(), d.creationTime.truncatedTo(ChronoUnit.MILLIS)); var devices = withDevices ? user.devices.stream().map(mapDevices).collect(Collectors.toSet()) : Set.of(); - return new UserDto(user.id, user.name, user.pictureUrl, user.email, devices, user.publicKey, user.privateKey, user.setupCode); + return new UserDto(user.getId(), user.getName(), user.getPictureUrl(), user.getEmail(), devices, user.getPublicKey(), user.getPrivateKey(), user.getSetupCode()); } @POST @@ -125,13 +127,13 @@ public UserDto getMe(@QueryParam("withDevices") boolean withDevices) { @Operation(summary = "resets the user account") @APIResponse(responseCode = "204", description = "deleted keys, devices and access permissions") public Response resetMe() { - User user = User.findById(jwt.getSubject()); - user.publicKey = null; - user.privateKey = null; - user.setupCode = null; - user.persist(); - Device.deleteByOwner(user.id); - accessTokenRepo.deleteByUser(user.id); + User user = userRepo.findById(jwt.getSubject()); + user.setPublicKey(null); + user.setPrivateKey(null); + user.setSetupCode(null); + userRepo.persist(user); + Device.deleteByOwner(user.getId()); + accessTokenRepo.deleteByUser(user.getId()); return Response.noContent().build(); } @@ -141,7 +143,7 @@ public Response resetMe() { @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "list all users") public List getAll() { - return User.findAll().stream().map(UserDto::justPublicInfo).toList(); + return userRepo.findAll().stream().map(UserDto::justPublicInfo).toList(); } } \ No newline at end of file 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 26d9edef3..d4197c38d 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -33,11 +33,12 @@ import org.cryptomator.hub.entities.AccessToken; import org.cryptomator.hub.entities.AccessTokenRepository; import org.cryptomator.hub.entities.Authority; -import org.cryptomator.hub.entities.EffectiveGroupMembership; import org.cryptomator.hub.entities.EffectiveVaultAccess; import org.cryptomator.hub.entities.Group; +import org.cryptomator.hub.entities.GroupRepository; import org.cryptomator.hub.entities.LegacyAccessToken; import org.cryptomator.hub.entities.User; +import org.cryptomator.hub.entities.UserRepository; import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.VaultAccess; import org.cryptomator.hub.entities.events.VaultAccessGrantedEventRepository; @@ -92,6 +93,10 @@ public class VaultResource { VaultOwnershipClaimedEventRepository vaultOwnershipClaimedEventRepo; @Inject VaultUpdatedEventRepository vaultUpdatedEventRepo; + @Inject + GroupRepository groupRepo; + @Inject + UserRepository userRepo; @Inject JsonWebToken jwt; @@ -173,10 +178,10 @@ public List getDirectMembers(@PathParam("vaultId") UUID vaultId) { @ActiveLicense public Response addUser(@PathParam("vaultId") UUID vaultId, @PathParam("userId") @ValidId String userId, @QueryParam("role") @DefaultValue("MEMBER") VaultAccess.Role role) { var vault = Vault.findById(vaultId); // should always be found, since @VaultRole filter would have triggered - var user = User.findByIdOptional(userId).orElseThrow(NotFoundException::new); + var user = userRepo.findByIdOptional(userId).orElseThrow(NotFoundException::new); var usedSeats = EffectiveVaultAccess.countSeatOccupyingUsers(); //check if license seats are free - if (usedSeats < license.getSeats()) { + if (usedSeats < license.getSeats()) { return addAuthority(vault, user, role); } // else check, if all seats are taken, but the person to add is already sitting @@ -203,10 +208,10 @@ public Response addUser(@PathParam("vaultId") UUID vaultId, @PathParam("userId") @ActiveLicense public Response addGroup(@PathParam("vaultId") UUID vaultId, @PathParam("groupId") @ValidId String groupId, @QueryParam("role") @DefaultValue("MEMBER") VaultAccess.Role role) { var vault = Vault.findById(vaultId); // should always be found, since @VaultRole filter would have triggered - var group = Group.findByIdOptional(groupId).orElseThrow(NotFoundException::new); + var group = groupRepo.findByIdOptional(groupId).orElseThrow(NotFoundException::new); //usersInGroup - usersInGroupAndPartOfAtLeastOneVault + usersOfAtLeastOneVault - if (EffectiveGroupMembership.countEffectiveGroupUsers(groupId) - EffectiveVaultAccess.countSeatOccupyingUsersOfGroup(groupId) + EffectiveVaultAccess.countSeatOccupyingUsers() > license.getSeats()) { + if (userRepo.countEffectiveGroupUsers(groupId) - EffectiveVaultAccess.countSeatOccupyingUsersOfGroup(groupId) + EffectiveVaultAccess.countSeatOccupyingUsers() > license.getSeats()) { throw new PaymentRequiredException("Number of effective vault users greater than or equal to the available license seats"); } @@ -214,13 +219,13 @@ public Response addGroup(@PathParam("vaultId") UUID vaultId, @PathParam("groupId } private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role role) { - var id = new VaultAccess.Id(vault.id, authority.id); + var id = new VaultAccess.Id(vault.id, authority.getId()); var existingAccess = VaultAccess.findByIdOptional(id); if (existingAccess.isPresent()) { var access = existingAccess.get(); access.role = role; access.persist(); - vaultMemberUpdatedEventRepo.log(jwt.getSubject(), vault.id, authority.id, role); + vaultMemberUpdatedEventRepo.log(jwt.getSubject(), vault.id, authority.getId(), role); return Response.ok().build(); } else { var access = new VaultAccess(); @@ -228,7 +233,7 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role access.authority = authority; access.role = role; access.persist(); - vaultMemberAddedEventRepo.log(jwt.getSubject(), vault.id, authority.id, role); + vaultMemberAddedEventRepo.log(jwt.getSubject(), vault.id, authority.getId(), role); return Response.created(URI.create(".")).build(); } } @@ -261,7 +266,7 @@ public Response removeAuthority(@PathParam("vaultId") UUID vaultId, @PathParam(" @APIResponse(responseCode = "200") @APIResponse(responseCode = "403", description = "not a vault owner") public List getUsersRequiringAccessGrant(@PathParam("vaultId") UUID vaultId) { - return User.findRequiringAccessGrant(vaultId).map(UserDto::justPublicInfo).toList(); + return userRepo.findRequiringAccessGrant(vaultId).map(UserDto::justPublicInfo).toList(); } @Deprecated(forRemoval = true) @@ -324,8 +329,8 @@ public Response unlock(@PathParam("vaultId") UUID vaultId, @QueryParam("evenIfAr throw new PaymentRequiredException("Number of effective vault users exceeds available license seats"); } - var user = User.findById(jwt.getSubject()); - if (user.publicKey == null) { + var user = userRepo.findById(jwt.getSubject()); + if (user.getPublicKey() == null) { throw new ActionRequiredException("User account not initialized."); } @@ -375,7 +380,7 @@ public Response grantAccess(@PathParam("vaultId") UUID vaultId, @NotEmpty MapfindByIdOptional(userId).orElseThrow(NotFoundException::new)); + token.setUser(userRepo.findByIdOptional(userId).orElseThrow(NotFoundException::new)); } token.setVaultKey(entry.getValue()); accessTokenRepo.persist(token); @@ -395,7 +400,7 @@ public Response grantAccess(@PathParam("vaultId") UUID vaultId, @NotEmpty MapfindByIdOptional(vaultId).orElseThrow(NotFoundException::new); - if (vault.effectiveMembers.stream().noneMatch(u -> u.id.equals(jwt.getSubject())) && !identity.getRoles().contains("admin")) { + if (vault.effectiveMembers.stream().noneMatch(u -> u.getId().equals(jwt.getSubject())) && !identity.getRoles().contains("admin")) { throw new ForbiddenException("Requesting user is not a member of the vault"); } return VaultDto.fromEntity(vault); @@ -414,7 +419,7 @@ public VaultDto get(@PathParam("vaultId") UUID vaultId) { @APIResponse(responseCode = "201", description = "new vault created") @APIResponse(responseCode = "402", description = "number of licensed seats is exceeded") public Response createOrUpdate(@PathParam("vaultId") UUID vaultId, @Valid @NotNull VaultDto vaultDto) { - User currentUser = User.findById(jwt.getSubject()); + User currentUser = userRepo.findById(jwt.getSubject()); Optional existingVault = Vault.findByIdOptional(vaultId); final Vault vault; if (existingVault.isPresent()) { @@ -438,17 +443,17 @@ public Response createOrUpdate(@PathParam("vaultId") UUID vaultId, @Valid @NotNu vault.persistAndFlush(); // trigger PersistenceException before we continue with if (existingVault.isEmpty()) { - vaultCreatedEventRepo.log(currentUser.id, vault.id, vault.name, vault.description); + vaultCreatedEventRepo.log(currentUser.getId(), vault.id, vault.name, vault.description); var access = new VaultAccess(); access.vault = vault; access.authority = currentUser; access.role = VaultAccess.Role.OWNER; access.persist(); - vaultMemberAddedEventRepo.log(currentUser.id, vaultId, currentUser.id, VaultAccess.Role.OWNER); + vaultMemberAddedEventRepo.log(currentUser.getId(), vaultId, currentUser.getId(), VaultAccess.Role.OWNER); return Response.created(URI.create(".")).contentLocation(URI.create(".")).entity(VaultDto.fromEntity(vault)).type(MediaType.APPLICATION_JSON).build(); } else { - vaultUpdatedEventRepo.log(currentUser.id, vault.id, vault.name, vault.description, vault.archived); + vaultUpdatedEventRepo.log(currentUser.getId(), vault.id, vault.name, vault.description, vault.archived); return Response.ok(VaultDto.fromEntity(vault), MediaType.APPLICATION_JSON).build(); } } @@ -465,7 +470,7 @@ public Response createOrUpdate(@PathParam("vaultId") UUID vaultId, @Valid @NotNu @APIResponse(responseCode = "404", description = "no such vault") @APIResponse(responseCode = "409", description = "owned by another user") public Response claimOwnership(@PathParam("vaultId") UUID vaultId, @FormParam("proof") @Valid @ValidJWS String proof) { - User currentUser = User.findById(jwt.getSubject()); + User currentUser = userRepo.findById(jwt.getSubject()); Vault vault = Vault.findByIdOptional(vaultId).orElseThrow(NotFoundException::new); // if vault.authenticationPublicKey no longer exists, this vault has already been claimed by a different user @@ -476,7 +481,7 @@ public Response claimOwnership(@PathParam("vaultId") UUID vaultId, @FormParam("p .acceptLeeway(30) .withClaimPresence("nbf") .withClaimPresence("exp") - .withSubject(currentUser.id) + .withSubject(currentUser.getId()) .withClaim("vaultId", vaultId.toString().toLowerCase()) .build(); verifier.verify(proof); @@ -484,19 +489,19 @@ public Response claimOwnership(@PathParam("vaultId") UUID vaultId, @FormParam("p throw new BadRequestException("Invalid proof of ownership", e); } - Optional existingAccess = VaultAccess.findByIdOptional(new VaultAccess.Id(vaultId, currentUser.id)); + Optional existingAccess = VaultAccess.findByIdOptional(new VaultAccess.Id(vaultId, currentUser.getId())); if (existingAccess.isPresent()) { var access = existingAccess.get(); access.role = VaultAccess.Role.OWNER; access.persist(); - vaultMemberUpdatedEventRepo.log(currentUser.id, vaultId, currentUser.id, VaultAccess.Role.OWNER); + vaultMemberUpdatedEventRepo.log(currentUser.getId(), vaultId, currentUser.getId(), VaultAccess.Role.OWNER); } else { var access = new VaultAccess(); access.vault = vault; access.authority = currentUser; access.role = VaultAccess.Role.OWNER; access.persist(); - vaultMemberAddedEventRepo.log(currentUser.id, vaultId, currentUser.id, VaultAccess.Role.OWNER); + vaultMemberAddedEventRepo.log(currentUser.getId(), vaultId, currentUser.getId(), VaultAccess.Role.OWNER); } vault.salt = null; @@ -506,7 +511,7 @@ public Response claimOwnership(@PathParam("vaultId") UUID vaultId, @FormParam("p vault.authenticationPublicKey = null; vault.persist(); - vaultOwnershipClaimedEventRepo.log(currentUser.id, vaultId); + vaultOwnershipClaimedEventRepo.log(currentUser.getId(), vaultId); return Response.ok(VaultDto.fromEntity(vault), MediaType.APPLICATION_JSON).build(); } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/Authority.java b/backend/src/main/java/org/cryptomator/hub/entities/Authority.java index d9dc1600a..f63495e85 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Authority.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Authority.java @@ -1,7 +1,5 @@ package org.cryptomator.hub.entities; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.panache.common.Parameters; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorColumn; import jakarta.persistence.Entity; @@ -11,9 +9,7 @@ import jakarta.persistence.NamedQuery; import jakarta.persistence.Table; -import java.util.List; import java.util.Objects; -import java.util.stream.Stream; @Entity @Table(name = "authority") @@ -31,21 +27,29 @@ WHERE LOWER(a.name) LIKE :name FROM Authority a WHERE a.id IN :ids """) -public class Authority extends PanacheEntityBase { // TODO make sealed? +public class Authority { @Id @Column(name = "id", nullable = false) - public String id; + String id; @Column(name = "name", nullable = false) - public String name; + String name; - public static Stream byName(String name) { - return find("#Authority.byName", Parameters.with("name", '%' + name.toLowerCase() + '%')).stream(); + public String getId() { + return id; } - public static Stream findAllInList(List ids) { - return find("#Authority.allInList", Parameters.with("ids", ids)).stream(); + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; } @Override diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AuthorityRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/AuthorityRepository.java new file mode 100644 index 000000000..bdd789194 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/AuthorityRepository.java @@ -0,0 +1,21 @@ +package org.cryptomator.hub.entities; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import io.quarkus.panache.common.Parameters; +import jakarta.enterprise.context.ApplicationScoped; + +import java.util.List; +import java.util.stream.Stream; + +@ApplicationScoped +public class AuthorityRepository implements PanacheRepository { + + public Stream byName(String name) { + return find("#Authority.byName", Parameters.with("name", '%' + name.toLowerCase() + '%')).stream(); + } + + public Stream findAllInList(List ids) { + return find("#Authority.allInList", Parameters.with("ids", ids)).stream(); + } + +} diff --git a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java deleted file mode 100644 index fbcf90952..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.cryptomator.hub.entities; - -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.panache.common.Parameters; -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import jakarta.persistence.EmbeddedId; -import jakarta.persistence.Entity; -import jakarta.persistence.NamedQuery; -import jakarta.persistence.Table; -import org.hibernate.annotations.Immutable; - -import java.io.Serializable; -import java.util.Objects; -import java.util.stream.Stream; - -@Entity -@Immutable -@Table(name = "effective_group_membership") -@NamedQuery(name = "EffectiveGroupMembership.countEGUs", query = """ - SELECT count( DISTINCT u) - FROM User u - INNER JOIN EffectiveGroupMembership egm ON u.id = egm.id.memberId - WHERE egm.id.groupId = :groupId - """) -@NamedQuery(name = "EffectiveGroupMembership.getEGUs", query = """ - SELECT DISTINCT u - FROM User u - INNER JOIN EffectiveGroupMembership egm ON u.id = egm.id.memberId - WHERE egm.id.groupId = :groupId - """) -public class EffectiveGroupMembership extends PanacheEntityBase { - - @EmbeddedId - public EffectiveGroupMembershipId id; - - public String path; - - public static long countEffectiveGroupUsers(String groupdId) { - return EffectiveGroupMembership.count("#EffectiveGroupMembership.countEGUs", Parameters.with("groupId", groupdId)); - } - - public static Stream getEffectiveGroupUsers(String groupdId) { - return EffectiveGroupMembership.find("#EffectiveGroupMembership.getEGUs", Parameters.with("groupId", groupdId)).stream(); - } - - @Embeddable - public static class EffectiveGroupMembershipId implements Serializable { - - @Column(name = "group_id") - public String groupId; - - @Column(name = "member_id") - public String memberId; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o instanceof EffectiveGroupMembershipId egmId) { - return Objects.equals(groupId, egmId.groupId) // - && Objects.equals(memberId, egmId.memberId); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(groupId, memberId); - } - - @Override - public String toString() { - return "EffectiveGroupMembershipId{" + - "groupId='" + groupId + '\'' + - ", memberId='" + memberId + '\'' + - '}'; - } - } -} 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 9d2222efc..55f5c1065 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Group.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Group.java @@ -20,6 +20,13 @@ public class Group extends Authority { joinColumns = @JoinColumn(name = "group_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "member_id", referencedColumnName = "id") ) - public Set members = new HashSet<>(); + Set members = new HashSet<>(); + public Set getMembers() { + return members; + } + + public void setMembers(Set members) { + this.members = members; + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/GroupRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/GroupRepository.java new file mode 100644 index 000000000..f562816bd --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/GroupRepository.java @@ -0,0 +1,8 @@ +package org.cryptomator.hub.entities; + +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class GroupRepository implements PanacheRepositoryBase { +} 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 927750504..07cd44a5b 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/User.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/User.java @@ -1,6 +1,5 @@ package org.cryptomator.hub.entities; -import io.quarkus.panache.common.Parameters; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -12,8 +11,6 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; -import java.util.UUID; -import java.util.stream.Stream; @Entity @Table(name = "user_details") @@ -27,22 +24,90 @@ WHERE perm.id.vaultId = :vaultId AND token.vault IS NULL AND u.publicKey IS NOT NULL """ ) +@NamedQuery(name = "User.getEffectiveGroupUsers", query = """ + SELECT DISTINCT u + FROM User u + INNER JOIN EffectiveGroupMembership egm ON u.id = egm.id.memberId + WHERE egm.id.groupId = :groupId + """) +@NamedQuery(name = "User.countEffectiveGroupUsers", query = """ + SELECT count( DISTINCT u) + FROM User u + INNER JOIN EffectiveGroupMembership egm ON u.id = egm.id.memberId + WHERE egm.id.groupId = :groupId + """) public class User extends Authority { @Column(name = "picture_url") - public String pictureUrl; + String pictureUrl; @Column(name = "email") - public String email; + String email; @Column(name = "publickey") - public String publicKey; + String publicKey; @Column(name = "privatekey") - public String privateKey; + String privateKey; @Column(name = "setupcode") - public String setupCode; + String setupCode; + + public String getPictureUrl() { + return pictureUrl; + } + + public void setPictureUrl(String pictureUrl) { + this.pictureUrl = pictureUrl; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + + public String getSetupCode() { + return setupCode; + } + + public void setSetupCode(String setupCode) { + this.setupCode = setupCode; + } + + public Set getAccessTokens() { + return accessTokens; + } + + public void setAccessTokens(Set accessTokens) { + this.accessTokens = accessTokens; + } + + public Set getDevices() { + return devices; + } + + public void setDevices(Set devices) { + this.devices = devices; + } @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) public Set accessTokens = new HashSet<>(); @@ -68,8 +133,4 @@ public int hashCode() { return Objects.hash(id, pictureUrl, email, publicKey, privateKey, setupCode); } - public static Stream findRequiringAccessGrant(UUID vaultId) { - return find("#User.requiringAccessGrant", Parameters.with("vaultId", vaultId)).stream(); - } - } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/UserRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/UserRepository.java new file mode 100644 index 000000000..ba5a2237c --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/UserRepository.java @@ -0,0 +1,24 @@ +package org.cryptomator.hub.entities; + +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import io.quarkus.panache.common.Parameters; +import jakarta.enterprise.context.ApplicationScoped; + +import java.util.UUID; +import java.util.stream.Stream; + +@ApplicationScoped +public class UserRepository implements PanacheRepositoryBase { + + public Stream findRequiringAccessGrant(UUID vaultId) { + return find("#User.requiringAccessGrant", Parameters.with("vaultId", vaultId)).stream(); + } + + public long countEffectiveGroupUsers(String groupdId) { + return count("#User.countEffectiveGroupUsers", Parameters.with("groupId", groupdId)); + } + + public Stream getEffectiveGroupUsers(String groupdId) { + return find("#User.getEffectiveGroupUsers", Parameters.with("groupId", groupdId)).stream(); + } +} diff --git a/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java b/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java index 2e2096e66..4cc4e208e 100644 --- a/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java +++ b/backend/src/test/java/org/cryptomator/hub/KeycloakRemoteUserProviderTest.java @@ -1,5 +1,6 @@ package org.cryptomator.hub; +import org.cryptomator.hub.entities.Authority; import org.cryptomator.hub.entities.User; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -78,15 +79,15 @@ void testListUser() { var resultUser1 = result.get(0); var resultUser2 = result.get(1); - Assertions.assertEquals("id3000", resultUser1.id); - Assertions.assertEquals("username3000", resultUser1.name); - Assertions.assertEquals("email3000", resultUser1.email); - Assertions.assertEquals("picture3000", resultUser1.pictureUrl); + Assertions.assertEquals("id3000", resultUser1.getId()); + Assertions.assertEquals("username3000", resultUser1.getName()); + Assertions.assertEquals("email3000", resultUser1.getEmail()); + Assertions.assertEquals("picture3000", resultUser1.getPictureUrl()); - Assertions.assertEquals("id3001", resultUser2.id); - Assertions.assertEquals("username3001", resultUser2.name); - Assertions.assertEquals("email3001", resultUser2.email); - Assertions.assertNull(resultUser2.pictureUrl); + Assertions.assertEquals("id3001", resultUser2.getId()); + Assertions.assertEquals("username3001", resultUser2.getName()); + Assertions.assertEquals("email3001", resultUser2.getEmail()); + Assertions.assertNull(resultUser2.getPictureUrl()); } @Test @@ -116,18 +117,18 @@ void testListUserIncludingHubCliUser() { var resultUser2 = result.get(1); var resultUser3 = result.get(2); - Assertions.assertEquals("id3000", resultUser1.id); - Assertions.assertEquals("username3000", resultUser1.name); - Assertions.assertEquals("email3000", resultUser1.email); - Assertions.assertEquals("picture3000", resultUser1.pictureUrl); + Assertions.assertEquals("id3000", resultUser1.getId()); + Assertions.assertEquals("username3000", resultUser1.getName()); + Assertions.assertEquals("email3000", resultUser1.getEmail()); + Assertions.assertEquals("picture3000", resultUser1.getPictureUrl()); - Assertions.assertEquals("id3001", resultUser2.id); - Assertions.assertEquals("username3001", resultUser2.name); - Assertions.assertEquals("email3001", resultUser2.email); - Assertions.assertNull(resultUser2.pictureUrl); + Assertions.assertEquals("id3001", resultUser2.getId()); + Assertions.assertEquals("username3001", resultUser2.getName()); + Assertions.assertEquals("email3001", resultUser2.getEmail()); + Assertions.assertNull(resultUser2.getPictureUrl()); - Assertions.assertEquals("cryptomatorHubCliUserId", resultUser3.id); - Assertions.assertEquals("cryptomatorHubCliUserUsername", resultUser3.name); + Assertions.assertEquals("cryptomatorHubCliUserId", resultUser3.getId()); + Assertions.assertEquals("cryptomatorHubCliUserUsername", resultUser3.getName()); } @@ -170,27 +171,27 @@ public void testListGroups() { var resultGroup1 = result.get(0); var resultGroup2 = result.get(1); - Assertions.assertEquals("grpId3000", resultGroup1.id); - Assertions.assertEquals("grpName3000", resultGroup1.name); - Assertions.assertEquals(0, resultGroup1.members.size()); + Assertions.assertEquals("grpId3000", resultGroup1.getId()); + Assertions.assertEquals("grpName3000", resultGroup1.getName()); + Assertions.assertEquals(0, resultGroup1.getMembers().size()); - Assertions.assertEquals("grpId3001", resultGroup2.id); - Assertions.assertEquals("grpName3001", resultGroup2.name); - Assertions.assertEquals(2, resultGroup2.members.size()); + Assertions.assertEquals("grpId3001", resultGroup2.getId()); + Assertions.assertEquals("grpName3001", resultGroup2.getName()); + Assertions.assertEquals(2, resultGroup2.getMembers().size()); - var membersGroup2 = resultGroup2.members.stream().sorted(Comparator.comparing(a -> a.id)).toList(); + var membersGroup2 = resultGroup2.getMembers().stream().sorted(Comparator.comparing(Authority::getId)).toList(); var member1Group2 = (User) membersGroup2.get(0); var member2Group2 = (User) membersGroup2.get(1); - Assertions.assertEquals("id3000", member1Group2.id); - Assertions.assertEquals("username3000", member1Group2.name); - Assertions.assertEquals("email3000", member1Group2.email); - Assertions.assertEquals("picture3000", member1Group2.pictureUrl); + Assertions.assertEquals("id3000", member1Group2.getId()); + Assertions.assertEquals("username3000", member1Group2.getName()); + Assertions.assertEquals("email3000", member1Group2.getEmail()); + Assertions.assertEquals("picture3000", member1Group2.getPictureUrl()); - Assertions.assertEquals("id3001", member2Group2.id); - Assertions.assertEquals("username3001", member2Group2.name); - Assertions.assertEquals("email3001", member2Group2.email); - Assertions.assertNull(member2Group2.pictureUrl); + Assertions.assertEquals("id3001", member2Group2.getId()); + Assertions.assertEquals("username3001", member2Group2.getName()); + Assertions.assertEquals("email3001", member2Group2.getEmail()); + Assertions.assertNull(member2Group2.getPictureUrl()); } } diff --git a/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java b/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java index e27586d45..334b61829 100644 --- a/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java +++ b/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java @@ -1,8 +1,10 @@ package org.cryptomator.hub; import org.cryptomator.hub.entities.Authority; +import org.cryptomator.hub.entities.AuthorityRepository; import org.cryptomator.hub.entities.Group; import org.cryptomator.hub.entities.User; +import org.cryptomator.hub.entities.UserRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -24,6 +26,8 @@ class RemoteUserPullerTest { private final RemoteUserProvider remoteUserProvider = Mockito.mock(RemoteUserProvider.class); private final User user = Mockito.mock(User.class); + private final AuthorityRepository authorityRepo = Mockito.mock(AuthorityRepository.class); + private final UserRepository userRepo = Mockito.mock(UserRepository.class); private RemoteUserPuller remoteUserPuller; @@ -65,7 +69,7 @@ public void testAddAuthorities(@ConvertWith(StringArrayConverter.class) String[] remoteUserPuller.syncAddedAuthorities(keycloakAuthorities, databaseAuthorities); for (String authorityId : addedAuthorityIds) { - Mockito.verify(keycloakAuthorities.get(authorityId)).persist(); + Mockito.verify(authorityRepo).persist(keycloakAuthorities.get(authorityId)); } } @@ -97,7 +101,7 @@ public void testDeleteAuthorities(@ConvertWith(StringArrayConverter.class) Strin remoteUserPuller.syncDeletedAuthorities(keycloakAuthorities, databaseAuthorities); for (String authorityId : deletedAuthorityIds) { - Mockito.verify(databaseAuthorities.get(authorityId)).delete(); + Mockito.verify(authorityRepo).delete(databaseAuthorities.get(authorityId)); } } @@ -132,9 +136,9 @@ public void testUpdateUsers(@ConvertWith(StringArrayConverter.class) String[] ke for (String userId : updatedUserIds) { var kcUser = Mockito.mock(User.class); - kcUser.pictureUrl = String.format("picture %s", userId); - kcUser.name = String.format("name %s", userId); - kcUser.email = String.format("email %s", userId); + kcUser.setPictureUrl(String.format("picture %s", userId)); + kcUser.setName(String.format("name %s", userId)); + kcUser.setEmail(String.format("email %s", userId)); Mockito.when(keycloakUsers.get(userId)).thenReturn(kcUser); Mockito.when(databaseUsers.get(userId)).thenReturn(Mockito.mock(User.class)); @@ -144,10 +148,10 @@ public void testUpdateUsers(@ConvertWith(StringArrayConverter.class) String[] ke for (String userId : updatedUserIds) { var dbUser = databaseUsers.get(userId); - Mockito.verify(dbUser).persist(); - Assertions.assertEquals(String.format("picture %s", userId), dbUser.pictureUrl); - Assertions.assertEquals(String.format("name %s", userId), dbUser.name); - Assertions.assertEquals(String.format("email %s", userId), dbUser.email); + Mockito.verify(userRepo).persist(dbUser); + Assertions.assertEquals(String.format("picture %s", userId), dbUser.getPictureUrl()); + Assertions.assertEquals(String.format("name %s", userId), dbUser.getName()); + Assertions.assertEquals(String.format("email %s", userId), dbUser.getEmail()); } } @@ -176,12 +180,12 @@ public void testUpdateGroups(@ConvertWith(StringArrayConverter.class) String[] k for (String groupId : updatedGroupIds) { var kcGroup = Mockito.mock(Group.class); - kcGroup.name = String.format("name %s", groupId); - kcGroup.members = Set.of(user, otherKCUser); + kcGroup.setName(String.format("name %s", groupId)); + kcGroup.setMembers(Set.of(user, otherKCUser)); var dbGroup = Mockito.mock(Group.class); - dbGroup.name = String.format("name %s", groupId); - dbGroup.members = new HashSet<>(Set.of(dbOnlyUser)); + dbGroup.setName(String.format("name %s", groupId)); + dbGroup.setMembers(new HashSet<>(Set.of(dbOnlyUser))); Mockito.when(keycloakGroups.get(groupId)).thenReturn(kcGroup); Mockito.when(databaseGroups.get(groupId)).thenReturn(dbGroup); @@ -191,8 +195,8 @@ public void testUpdateGroups(@ConvertWith(StringArrayConverter.class) String[] k for (String groupId : updatedGroupIds) { var dbGroup = databaseGroups.get(groupId); - Assertions.assertEquals(String.format("name %s", groupId), dbGroup.name); - Assertions.assertEquals(Set.of(user, otherKCUser), dbGroup.members); + Assertions.assertEquals(String.format("name %s", groupId), dbGroup.getName()); + Assertions.assertEquals(Set.of(user, otherKCUser), dbGroup.getMembers()); } } } diff --git a/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java b/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java index b58d1f122..978013001 100644 --- a/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java +++ b/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java @@ -18,6 +18,8 @@ public class EntityIT { @Inject AccessTokenRepository accessTokenRepo; @Inject + UserRepository userRepo; + @Inject AgroalDataSource dataSource; @Test @@ -33,7 +35,7 @@ public void removingUserCascadesToAccess() throws SQLException { """); } - var deleted = User.deleteById("user999"); + var deleted = userRepo.deleteById("user999"); var matchAfter = accessTokenRepo.findAll().stream().anyMatch(a -> "user999".equals(a.user.id)); Assertions.assertTrue(deleted); Assertions.assertFalse(matchAfter); From a15f3295cd600b701b946a6d4046230c9e89eaed Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 20 Mar 2024 11:02:04 +0100 Subject: [PATCH 27/54] migrate Device entitiy to repository pattern --- .../cryptomator/hub/api/DeviceResource.java | 41 ++++----- .../cryptomator/hub/api/UsersResource.java | 7 +- .../org/cryptomator/hub/entities/Device.java | 84 ++++++++++++++----- .../hub/entities/DeviceRepository.java | 27 ++++++ 4 files changed, 118 insertions(+), 41 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/DeviceRepository.java 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 b90082ee0..a13feface 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java @@ -21,6 +21,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.Device; +import org.cryptomator.hub.entities.DeviceRepository; import org.cryptomator.hub.entities.LegacyAccessToken; import org.cryptomator.hub.entities.LegacyDevice; import org.cryptomator.hub.entities.User; @@ -58,6 +59,8 @@ public class DeviceResource { @Inject UserRepository userRepo; @Inject + DeviceRepository deviceRepo; + @Inject JsonWebToken jwt; @GET @@ -68,7 +71,7 @@ public class DeviceResource { @Operation(summary = "lists all devices matching the given ids", description = "lists for each id in the list its corresponding device. Ignores all id's where a device cannot be found") @APIResponse(responseCode = "200") public List getSome(@QueryParam("ids") List deviceIds) { - return Device.findAllInList(deviceIds).map(DeviceDto::fromEntity).toList(); + return deviceRepo.findAllInList(deviceIds).map(DeviceDto::fromEntity).toList(); } @PUT @@ -83,26 +86,26 @@ public List getSome(@QueryParam("ids") List deviceIds) { public Response createOrUpdate(@Valid @NotNull DeviceDto dto, @PathParam("deviceId") @ValidId String deviceId) { Device device; try { - device = Device.findByIdAndUser(deviceId, jwt.getSubject()); + device = deviceRepo.findByIdAndUser(deviceId, jwt.getSubject()); } catch (NoResultException e) { device = new Device(); - device.id = deviceId; - device.owner = userRepo.findById(jwt.getSubject()); - device.creationTime = Instant.now().truncatedTo(ChronoUnit.MILLIS); - device.type = dto.type != null ? dto.type : Device.Type.DESKTOP; // default to desktop for backwards compatibility + device.setId(deviceId); + device.setOwner(userRepo.findById(jwt.getSubject())); + device.setCreationTime(Instant.now().truncatedTo(ChronoUnit.MILLIS)); + device.setType(dto.type != null ? dto.type : Device.Type.DESKTOP); // default to desktop for backwards compatibilit); - if (LegacyDevice.deleteById(device.id)) { - assert LegacyDevice.findById(device.id) == null; + if (LegacyDevice.deleteById(device.getId())) { + assert LegacyDevice.findById(device.getId()) == null; LOG.info("Deleted Legacy Device during re-registration of Device " + deviceId); } } - device.name = dto.name; - device.publickey = dto.publicKey; - device.userPrivateKey = dto.userPrivateKey; + device.setName(dto.name); + device.setPublickey(dto.publicKey); + device.setUserPrivateKey(dto.userPrivateKey); try { - device.persistAndFlush(); - deviceRegisteredEventRepo.log(jwt.getSubject(), deviceId, device.name, device.type); + deviceRepo.persistAndFlush(device); + deviceRegisteredEventRepo.log(jwt.getSubject(), deviceId, device.getName(), device.getType()); return Response.created(URI.create(".")).build(); } catch (ConstraintViolationException e) { throw new ClientErrorException(Response.Status.CONFLICT, e); @@ -120,7 +123,7 @@ public Response createOrUpdate(@Valid @NotNull DeviceDto dto, @PathParam("device @APIResponse(responseCode = "404", description = "Device not found or owned by a different user") public DeviceDto get(@PathParam("deviceId") @ValidId String deviceId) { try { - Device device = Device.findByIdAndUser(deviceId, jwt.getSubject()); + Device device = deviceRepo.findByIdAndUser(deviceId, jwt.getSubject()); return DeviceDto.fromEntity(device); } catch (NoResultException e) { throw new NotFoundException(e); @@ -150,14 +153,14 @@ public Map getLegacyAccessTokens(@PathParam("deviceId") @ValidId S @APIResponse(responseCode = "204", description = "device removed") @APIResponse(responseCode = "404", description = "device not found with current user") public Response remove(@PathParam("deviceId") @ValidId String deviceId) { - if (deviceId == null || deviceId.trim().length() == 0) { + if (deviceId == null || deviceId.trim().isEmpty()) { return Response.status(Response.Status.BAD_REQUEST).entity("deviceId cannot be empty").build(); } User currentUser = userRepo.findById(jwt.getSubject()); - var maybeDevice = Device.findByIdOptional(deviceId); - if (maybeDevice.isPresent() && currentUser.equals(maybeDevice.get().owner)) { - maybeDevice.get().delete(); + var maybeDevice = deviceRepo.findByIdOptional(deviceId); + if (maybeDevice.isPresent() && currentUser.equals(maybeDevice.get().getOwner())) { + deviceRepo.delete(maybeDevice.get()); deviceRemovedEventRepo.log(jwt.getSubject(), deviceId); return Response.status(Response.Status.NO_CONTENT).build(); } else { @@ -174,7 +177,7 @@ public record DeviceDto(@JsonProperty("id") @ValidId String id, @JsonProperty("creationTime") Instant creationTime) { public static DeviceDto fromEntity(Device entity) { - return new DeviceDto(entity.id, entity.name, entity.type, entity.publickey, entity.userPrivateKey, entity.owner.getId(), entity.creationTime.truncatedTo(ChronoUnit.MILLIS)); + return new DeviceDto(entity.getId(), entity.getName(), entity.getType(), entity.getPublickey(), entity.getUserPrivateKey(), entity.getOwner().getId(), entity.getCreationTime().truncatedTo(ChronoUnit.MILLIS)); } } 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 dd1c5f952..2c7ac27f1 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java @@ -18,6 +18,7 @@ import org.cryptomator.hub.entities.AccessToken; import org.cryptomator.hub.entities.AccessTokenRepository; import org.cryptomator.hub.entities.Device; +import org.cryptomator.hub.entities.DeviceRepository; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.UserRepository; import org.cryptomator.hub.entities.Vault; @@ -46,6 +47,8 @@ public class UsersResource { VaultAccessGrantedEventRepository vaultAccessGrantedEventRepo; @Inject UserRepository userRepo; + @Inject + DeviceRepository deviceRepo; @Inject JsonWebToken jwt; @@ -114,7 +117,7 @@ public Response updateMyAccessTokens(@NotNull Map tokens) { @APIResponse(responseCode = "404", description = "no user matching the subject of the JWT passed as Bearer Token") public UserDto getMe(@QueryParam("withDevices") boolean withDevices) { User user = userRepo.findById(jwt.getSubject()); - Function mapDevices = d -> new DeviceResource.DeviceDto(d.id, d.name, d.type, d.publickey, d.userPrivateKey, d.owner.getId(), d.creationTime.truncatedTo(ChronoUnit.MILLIS)); + Function mapDevices = d -> new DeviceResource.DeviceDto(d.getId(), d.getName(), d.getType(), d.getPublickey(), d.getUserPrivateKey(), d.getOwner().getId(), d.getCreationTime().truncatedTo(ChronoUnit.MILLIS)); var devices = withDevices ? user.devices.stream().map(mapDevices).collect(Collectors.toSet()) : Set.of(); return new UserDto(user.getId(), user.getName(), user.getPictureUrl(), user.getEmail(), devices, user.getPublicKey(), user.getPrivateKey(), user.getSetupCode()); } @@ -132,7 +135,7 @@ public Response resetMe() { user.setPrivateKey(null); user.setSetupCode(null); userRepo.persist(user); - Device.deleteByOwner(user.getId()); + deviceRepo.deleteByOwner(user.getId()); accessTokenRepo.deleteByUser(user.getId()); return Response.noContent().build(); } 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 94e37a9e8..f0f854228 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Device.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Device.java @@ -31,7 +31,7 @@ FROM Device d WHERE d.id IN :ids """) -public class Device extends PanacheEntityBase { +public class Device { public enum Type { BROWSER, DESKTOP, MOBILE @@ -39,27 +39,83 @@ public enum Type { @Id @Column(name = "id", nullable = false) - public String id; + String id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "owner_id", updatable = false, nullable = false) - public User owner; + User owner; @Column(name = "name", nullable = false) - public String name; + String name; @Column(name = "type", nullable = false) @Enumerated(EnumType.STRING) - public Type type; + Type type; @Column(name = "publickey", nullable = false) - public String publickey; + String publickey; @Column(name = "user_privatekey", nullable = false) - public String userPrivateKey; + String userPrivateKey; @Column(name = "creation_time", nullable = false) - public Instant creationTime; + Instant creationTime; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public User getOwner() { + return owner; + } + + public void setOwner(User owner) { + this.owner = owner; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public String getPublickey() { + return publickey; + } + + public void setPublickey(String publickey) { + this.publickey = publickey; + } + + public String getUserPrivateKey() { + return userPrivateKey; + } + + public void setUserPrivateKey(String userPrivateKey) { + this.userPrivateKey = userPrivateKey; + } + + public Instant getCreationTime() { + return creationTime; + } + + public void setCreationTime(Instant creationTime) { + this.creationTime = creationTime; + } @Override public String toString() { @@ -93,16 +149,4 @@ public int hashCode() { return Objects.hash(id, owner, name, type, publickey, userPrivateKey, creationTime); } - public static Device findByIdAndUser(String deviceId, String userId) throws NoResultException { - return find("#Device.findByIdAndOwner", Parameters.with("deviceId", deviceId).and("userId", userId)).singleResult(); - } - - public static Stream findAllInList(List ids) { - return find("#Device.allInList", Parameters.with("ids", ids)).stream(); - } - - public static void deleteByOwner(String userId) { - delete("#Device.deleteByOwner", Parameters.with("userId", userId)); - } - } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/DeviceRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/DeviceRepository.java new file mode 100644 index 000000000..282c5b8c0 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/DeviceRepository.java @@ -0,0 +1,27 @@ +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.NoResultException; + +import java.util.List; +import java.util.stream.Stream; + +@ApplicationScoped +public class DeviceRepository implements PanacheRepositoryBase { + + public Device findByIdAndUser(String deviceId, String userId) throws NoResultException { + return find("#Device.findByIdAndOwner", Parameters.with("deviceId", deviceId).and("userId", userId)).singleResult(); + } + + public Stream findAllInList(List ids) { + return find("#Device.allInList", Parameters.with("ids", ids)).stream(); + } + + public void deleteByOwner(String userId) { + delete("#Device.deleteByOwner", Parameters.with("userId", userId)); + } + + +} From 8a209566de71cf0f5c503e2b47b9fe53096dff23 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 20 Mar 2024 12:25:10 +0100 Subject: [PATCH 28/54] fix remoteUserPuller test --- .../cryptomator/hub/RemoteUserPullerTest.java | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java b/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java index 334b61829..40554b461 100644 --- a/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java +++ b/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java @@ -5,6 +5,8 @@ import org.cryptomator.hub.entities.Group; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.UserRepository; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -20,6 +22,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; class RemoteUserPullerTest { @@ -35,6 +38,10 @@ class RemoteUserPullerTest { void setUp() { remoteUserPuller = new RemoteUserPuller(); remoteUserPuller.remoteUserProvider = remoteUserProvider; + remoteUserPuller.authorityRepo = authorityRepo; + remoteUserPuller.userRepo = userRepo; + Mockito.doNothing().when(authorityRepo).persist((Authority) Mockito.any()); + Mockito.doNothing().when(userRepo).persist((User) Mockito.any()); } @Nested @@ -63,7 +70,9 @@ public void testAddAuthorities(@ConvertWith(StringArrayConverter.class) String[] Mockito.when(databaseAuthorities.keySet()).thenReturn(databaseAuthorityIds); for (String authorityId : addedAuthorityIds) { - Mockito.when(keycloakAuthorities.get(authorityId)).thenReturn(Mockito.mock(TestAuthority.class)); + var authorityMock = Mockito.mock(TestAuthority.class); + Mockito.when(authorityMock.getId()).thenReturn(authorityId); + Mockito.when(keycloakAuthorities.get(authorityId)).thenReturn(authorityMock); } remoteUserPuller.syncAddedAuthorities(keycloakAuthorities, databaseAuthorities); @@ -73,7 +82,7 @@ public void testAddAuthorities(@ConvertWith(StringArrayConverter.class) String[] } } - @DisplayName("test delete users/groups") + @DisplayName("test delete authorities") @ParameterizedTest(name = "KCUAuthorities: {0} DBAuthorities: {1} DeletedAuthorities: {2}") @CsvSource(value = { "foo,bar,baz;,;,", @@ -89,20 +98,19 @@ public void testDeleteAuthorities(@ConvertWith(StringArrayConverter.class) Strin Set keycloakAuthorityIds = Arrays.stream(keycloakAuthorityIdString).collect(Collectors.toSet()); Set databaseAuthorityIds = Arrays.stream(databaseAuthorityIdString).collect(Collectors.toSet()); - Set deletedAuthorityIds = Arrays.stream(deletedAuthorityIdString).collect(Collectors.toSet()); + Map deletedAuthorityIds = Arrays.stream(deletedAuthorityIdString).collect(Collectors.toMap(Function.identity(), (id) -> Mockito.mock(TestAuthority.class))); + Mockito.when(keycloakAuthorities.keySet()).thenReturn(keycloakAuthorityIds); Mockito.when(databaseAuthorities.keySet()).thenReturn(databaseAuthorityIds); - for (String authorityId : deletedAuthorityIds) { - Mockito.when(databaseAuthorities.get(authorityId)).thenReturn(Mockito.mock(TestAuthority.class)); - } + deletedAuthorityIds.forEach((id, authority) -> + Mockito.when(keycloakAuthorities.get(id)).thenReturn(authority)); remoteUserPuller.syncDeletedAuthorities(keycloakAuthorities, databaseAuthorities); - for (String authorityId : deletedAuthorityIds) { - Mockito.verify(authorityRepo).delete(databaseAuthorities.get(authorityId)); - } + deletedAuthorityIds.forEach((id, authority) -> + Mockito.verify(authorityRepo).delete(authority)); } private static class TestAuthority extends Authority { @@ -136,9 +144,9 @@ public void testUpdateUsers(@ConvertWith(StringArrayConverter.class) String[] ke for (String userId : updatedUserIds) { var kcUser = Mockito.mock(User.class); - kcUser.setPictureUrl(String.format("picture %s", userId)); - kcUser.setName(String.format("name %s", userId)); - kcUser.setEmail(String.format("email %s", userId)); + Mockito.when(kcUser.getPictureUrl()).thenReturn(String.format("picture %s", userId)); + Mockito.when(kcUser.getName()).thenReturn(String.format("name %s", userId)); + Mockito.when(kcUser.getEmail()).thenReturn(String.format("email %s", userId)); Mockito.when(keycloakUsers.get(userId)).thenReturn(kcUser); Mockito.when(databaseUsers.get(userId)).thenReturn(Mockito.mock(User.class)); @@ -149,9 +157,9 @@ public void testUpdateUsers(@ConvertWith(StringArrayConverter.class) String[] ke for (String userId : updatedUserIds) { var dbUser = databaseUsers.get(userId); Mockito.verify(userRepo).persist(dbUser); - Assertions.assertEquals(String.format("picture %s", userId), dbUser.getPictureUrl()); - Assertions.assertEquals(String.format("name %s", userId), dbUser.getName()); - Assertions.assertEquals(String.format("email %s", userId), dbUser.getEmail()); + Mockito.verify(dbUser).setPictureUrl(String.format("picture %s", userId)); + Mockito.verify(dbUser).setName(String.format("name %s", userId)); + Mockito.verify(dbUser).setEmail(String.format("email %s", userId)); } } @@ -178,14 +186,14 @@ public void testUpdateGroups(@ConvertWith(StringArrayConverter.class) String[] k Mockito.when(keycloakGroups.keySet()).thenReturn(keycloakGroupIds); Mockito.when(databaseGroups.keySet()).thenReturn(databaseGroupIds); + var dbGroupMembers = new HashSet(Set.of(dbOnlyUser)); for (String groupId : updatedGroupIds) { - var kcGroup = Mockito.mock(Group.class); - kcGroup.setName(String.format("name %s", groupId)); - kcGroup.setMembers(Set.of(user, otherKCUser)); + var kcGroup = Mockito.mock(Group.class, "kcGroup"); + Mockito.when(kcGroup.getName()).thenReturn(String.format("name %s", groupId)); + Mockito.when(kcGroup.getMembers()).thenReturn(Set.of(user, otherKCUser)); - var dbGroup = Mockito.mock(Group.class); - dbGroup.setName(String.format("name %s", groupId)); - dbGroup.setMembers(new HashSet<>(Set.of(dbOnlyUser))); + var dbGroup = Mockito.mock(Group.class, "dbGroup"); + Mockito.when(dbGroup.getMembers()).thenReturn(dbGroupMembers); Mockito.when(keycloakGroups.get(groupId)).thenReturn(kcGroup); Mockito.when(databaseGroups.get(groupId)).thenReturn(dbGroup); @@ -195,8 +203,8 @@ public void testUpdateGroups(@ConvertWith(StringArrayConverter.class) String[] k for (String groupId : updatedGroupIds) { var dbGroup = databaseGroups.get(groupId); - Assertions.assertEquals(String.format("name %s", groupId), dbGroup.getName()); - Assertions.assertEquals(Set.of(user, otherKCUser), dbGroup.getMembers()); + Mockito.verify(dbGroup).setName(String.format("name %s", groupId)); + MatcherAssert.assertThat(dbGroupMembers, Matchers.containsInAnyOrder(user, otherKCUser)); } } } From 3537adc5ab9dcf6f18b7661c0553b448bbdac307 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 20 Mar 2024 12:28:44 +0100 Subject: [PATCH 29/54] revert deletion of EffectiveGroupMembership --- .../entities/EffectiveGroupMembership.java | 71 +++++++++++++++++++ ...{RollbackTest.java => RollbackTestIT.java} | 2 +- 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java rename backend/src/test/java/org/cryptomator/hub/{RollbackTest.java => RollbackTestIT.java} (98%) diff --git a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java new file mode 100644 index 000000000..6bf67291c --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java @@ -0,0 +1,71 @@ +package org.cryptomator.hub.entities; + +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; +import io.quarkus.panache.common.Parameters; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.NamedQuery; +import jakarta.persistence.Table; +import org.hibernate.annotations.Immutable; + +import java.io.Serializable; +import java.util.Objects; +import java.util.stream.Stream; + +@Entity +@Immutable +@Table(name = "effective_group_membership") +@NamedQuery(name = "EffectiveGroupMembership.countEGUs", query = """ + SELECT count( DISTINCT u) + FROM User u + INNER JOIN EffectiveGroupMembership egm ON u.id = egm.id.memberId + WHERE egm.id.groupId = :groupId + """) +@NamedQuery(name = "EffectiveGroupMembership.getEGUs", query = """ + SELECT DISTINCT u + FROM User u + INNER JOIN EffectiveGroupMembership egm ON u.id = egm.id.memberId + WHERE egm.id.groupId = :groupId + """) +public class EffectiveGroupMembership { + + @EmbeddedId + EffectiveGroupMembershipId id; + + String path; + + @Embeddable + public static class EffectiveGroupMembershipId implements Serializable { + + @Column(name = "group_id") + public String groupId; + + @Column(name = "member_id") + public String memberId; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof EffectiveGroupMembershipId egmId) { + return Objects.equals(groupId, egmId.groupId) // + && Objects.equals(memberId, egmId.memberId); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(groupId, memberId); + } + + @Override + public String toString() { + return "EffectiveGroupMembershipId{" + + "groupId='" + groupId + '\'' + + ", memberId='" + memberId + '\'' + + '}'; + } + } +} diff --git a/backend/src/test/java/org/cryptomator/hub/RollbackTest.java b/backend/src/test/java/org/cryptomator/hub/RollbackTestIT.java similarity index 98% rename from backend/src/test/java/org/cryptomator/hub/RollbackTest.java rename to backend/src/test/java/org/cryptomator/hub/RollbackTestIT.java index 94fe4f85a..43c7bfa98 100644 --- a/backend/src/test/java/org/cryptomator/hub/RollbackTest.java +++ b/backend/src/test/java/org/cryptomator/hub/RollbackTestIT.java @@ -12,7 +12,7 @@ import java.sql.SQLException; @QuarkusTest -public class RollbackTest { +public class RollbackTestIT { @Inject public Flyway flyway; From 57189f8731e6843e898e83da29d54f340b19f6e3 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 20 Mar 2024 12:53:24 +0100 Subject: [PATCH 30/54] cleanup --- .../cryptomator/hub/entities/AuthorityRepository.java | 4 ++-- .../hub/entities/EffectiveGroupMembership.java | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AuthorityRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/AuthorityRepository.java index bdd789194..e1b3e1eeb 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AuthorityRepository.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/AuthorityRepository.java @@ -1,6 +1,6 @@ package org.cryptomator.hub.entities; -import io.quarkus.hibernate.orm.panache.PanacheRepository; +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; @@ -8,7 +8,7 @@ import java.util.stream.Stream; @ApplicationScoped -public class AuthorityRepository implements PanacheRepository { +public class AuthorityRepository implements PanacheRepositoryBase { public Stream byName(String name) { return find("#Authority.byName", Parameters.with("name", '%' + name.toLowerCase() + '%')).stream(); 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 6bf67291c..ae800eb91 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java @@ -1,7 +1,5 @@ package org.cryptomator.hub.entities; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.panache.common.Parameters; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import jakarta.persistence.EmbeddedId; @@ -12,7 +10,6 @@ import java.io.Serializable; import java.util.Objects; -import java.util.stream.Stream; @Entity @Immutable @@ -32,12 +29,12 @@ SELECT count( DISTINCT u) public class EffectiveGroupMembership { @EmbeddedId - EffectiveGroupMembershipId id; + Id id; String path; @Embeddable - public static class EffectiveGroupMembershipId implements Serializable { + public static class Id implements Serializable { @Column(name = "group_id") public String groupId; @@ -48,7 +45,7 @@ public static class EffectiveGroupMembershipId implements Serializable { @Override public boolean equals(Object o) { if (this == o) return true; - if (o instanceof EffectiveGroupMembershipId egmId) { + if (o instanceof Id egmId) { return Objects.equals(groupId, egmId.groupId) // && Objects.equals(memberId, egmId.memberId); } From 25c52c8c56beb6cb2d3466089c82331e75a0a045 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 20 Mar 2024 13:35:41 +0100 Subject: [PATCH 31/54] migrate EffectiveVaultAccess to repository pattern --- .../cryptomator/hub/api/BillingResource.java | 23 +++---- .../cryptomator/hub/api/VaultResource.java | 20 +++--- .../hub/entities/EffectiveVaultAccess.java | 68 +++++++++---------- .../EffectiveVaultAccessRepository.java | 41 +++++++++++ .../hub/filters/VaultRoleFilter.java | 6 +- .../cryptomator/hub/api/VaultResourceIT.java | 20 +++--- .../hub/filters/VaultRoleFilterTest.java | 8 +++ 7 files changed, 121 insertions(+), 65 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccessRepository.java 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 b24827c95..32939d9c0 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/BillingResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/BillingResource.java @@ -14,7 +14,7 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.cryptomator.hub.entities.EffectiveVaultAccess; +import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; import org.cryptomator.hub.entities.Settings; import org.cryptomator.hub.license.LicenseHolder; import org.cryptomator.hub.validation.ValidJWS; @@ -29,6 +29,8 @@ public class BillingResource { @Inject LicenseHolder licenseHolder; + @Inject + EffectiveVaultAccessRepository effectiveVaultAccessRepo; @GET @Path("/") @@ -39,11 +41,13 @@ public class BillingResource { @APIResponse(responseCode = "200") @APIResponse(responseCode = "403", description = "only admins are allowed to get the billing information") public BillingDto get() { + int usedSeats = (int) effectiveVaultAccessRepo.countSeatOccupyingUsers(); + boolean isManaged = licenseHolder.isManagedInstance(); return Optional.ofNullable(licenseHolder.get()) - .map(jwt -> BillingDto.fromDecodedJwt(jwt, licenseHolder)) + .map(jwt -> BillingDto.fromDecodedJwt(jwt, usedSeats, isManaged)) .orElseGet(() -> { var hubId = Settings.get().hubId; - return BillingDto.create(hubId, licenseHolder); + return BillingDto.create(hubId, (int) licenseHolder.getNoLicenseSeats(), usedSeats, isManaged); }); } @@ -68,22 +72,17 @@ public record BillingDto(@JsonProperty("hubId") String hubId, @JsonProperty("has @JsonProperty("licensedSeats") Integer licensedSeats, @JsonProperty("usedSeats") Integer usedSeats, @JsonProperty("issuedAt") Instant issuedAt, @JsonProperty("expiresAt") Instant expiresAt, @JsonProperty("managedInstance") Boolean managedInstance) { - public static BillingDto create(String hubId, LicenseHolder licenseHolder) { - var licensedSeats = licenseHolder.getNoLicenseSeats(); - var usedSeats = EffectiveVaultAccess.countSeatOccupyingUsers(); - var managedInstance = licenseHolder.isManagedInstance(); - return new BillingDto(hubId, false, null, (int) licensedSeats, (int) usedSeats, null, null, managedInstance); + public static BillingDto create(String hubId, int noLicenseSeatCount, int usedSeats, boolean isManaged) { + return new BillingDto(hubId, false, null, noLicenseSeatCount, usedSeats, null, null, isManaged); } - public static BillingDto fromDecodedJwt(DecodedJWT jwt, LicenseHolder licenseHolder) { + public static BillingDto fromDecodedJwt(DecodedJWT jwt, int usedSeats, boolean isManaged) { var id = jwt.getId(); var email = jwt.getSubject(); var licensedSeats = jwt.getClaim("seats").asInt(); - var usedSeats = (int) EffectiveVaultAccess.countSeatOccupyingUsers(); var issuedAt = jwt.getIssuedAt().toInstant(); var expiresAt = jwt.getExpiresAt().toInstant(); - var managedInstance = licenseHolder.isManagedInstance(); - return new BillingDto(id, true, email, licensedSeats, usedSeats, issuedAt, expiresAt, managedInstance); + return new BillingDto(id, true, email, licensedSeats, usedSeats, issuedAt, expiresAt, isManaged); } } 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 d4197c38d..bcae223a7 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -33,7 +33,7 @@ import org.cryptomator.hub.entities.AccessToken; import org.cryptomator.hub.entities.AccessTokenRepository; import org.cryptomator.hub.entities.Authority; -import org.cryptomator.hub.entities.EffectiveVaultAccess; +import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; import org.cryptomator.hub.entities.Group; import org.cryptomator.hub.entities.GroupRepository; import org.cryptomator.hub.entities.LegacyAccessToken; @@ -97,6 +97,8 @@ public class VaultResource { GroupRepository groupRepo; @Inject UserRepository userRepo; + @Inject + EffectiveVaultAccessRepository effectiveVaultAccessRepo; @Inject JsonWebToken jwt; @@ -179,13 +181,13 @@ public List getDirectMembers(@PathParam("vaultId") UUID vaultId) { public Response addUser(@PathParam("vaultId") UUID vaultId, @PathParam("userId") @ValidId String userId, @QueryParam("role") @DefaultValue("MEMBER") VaultAccess.Role role) { var vault = Vault.findById(vaultId); // should always be found, since @VaultRole filter would have triggered var user = userRepo.findByIdOptional(userId).orElseThrow(NotFoundException::new); - var usedSeats = EffectiveVaultAccess.countSeatOccupyingUsers(); + var usedSeats = effectiveVaultAccessRepo.countSeatOccupyingUsers(); //check if license seats are free if (usedSeats < license.getSeats()) { return addAuthority(vault, user, role); } // else check, if all seats are taken, but the person to add is already sitting - if (usedSeats == license.getSeats() && EffectiveVaultAccess.isUserOccupyingSeat(userId)) { + if (usedSeats == license.getSeats() && effectiveVaultAccessRepo.isUserOccupyingSeat(userId)) { return addAuthority(vault, user, role); } //otherwise block @@ -211,7 +213,7 @@ public Response addGroup(@PathParam("vaultId") UUID vaultId, @PathParam("groupId var group = groupRepo.findByIdOptional(groupId).orElseThrow(NotFoundException::new); //usersInGroup - usersInGroupAndPartOfAtLeastOneVault + usersOfAtLeastOneVault - if (userRepo.countEffectiveGroupUsers(groupId) - EffectiveVaultAccess.countSeatOccupyingUsersOfGroup(groupId) + EffectiveVaultAccess.countSeatOccupyingUsers() > license.getSeats()) { + if (userRepo.countEffectiveGroupUsers(groupId) - effectiveVaultAccessRepo.countSeatOccupyingUsersOfGroup(groupId) + effectiveVaultAccessRepo.countSeatOccupyingUsers() > license.getSeats()) { throw new PaymentRequiredException("Number of effective vault users greater than or equal to the available license seats"); } @@ -287,7 +289,7 @@ public Response legacyUnlock(@PathParam("vaultId") UUID vaultId, @PathParam("dev throw new GoneException("Vault is archived."); } - var accessTokenSeats = EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken(); + var accessTokenSeats = effectiveVaultAccessRepo.countSeatOccupyingUsersWithAccessToken(); if (accessTokenSeats > license.getSeats()) { throw new PaymentRequiredException("Number of effective vault users exceeds available license seats"); } @@ -324,7 +326,7 @@ public Response unlock(@PathParam("vaultId") UUID vaultId, @QueryParam("evenIfAr throw new GoneException("Vault is archived."); } - var accessTokenSeats = EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken(); + var accessTokenSeats = effectiveVaultAccessRepo.countSeatOccupyingUsersWithAccessToken(); if (accessTokenSeats > license.getSeats()) { throw new PaymentRequiredException("Number of effective vault users exceeds available license seats"); } @@ -367,8 +369,8 @@ public Response grantAccess(@PathParam("vaultId") UUID vaultId, @NotEmpty Map license.getSeats()) { throw new PaymentRequiredException("Number of effective vault users greater than or equal to the available license seats"); @@ -427,7 +429,7 @@ public Response createOrUpdate(@PathParam("vaultId") UUID vaultId, @Valid @NotNu vault = existingVault.get(); } else { //if license is exceeded block vault creation, independent if the user is already sitting - var usedSeats = EffectiveVaultAccess.countSeatOccupyingUsers(); + var usedSeats = effectiveVaultAccessRepo.countSeatOccupyingUsers(); if (usedSeats > license.getSeats()) { throw new PaymentRequiredException("Number of effective vault users exceeds available license seats"); } 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 e4596edcb..6755162b0 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java @@ -1,7 +1,5 @@ package org.cryptomator.hub.entities; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.panache.common.Parameters; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import jakarta.persistence.EmbeddedId; @@ -13,17 +11,13 @@ import org.hibernate.annotations.Immutable; import java.io.Serializable; -import java.util.Collection; -import java.util.List; import java.util.Objects; import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; @Entity @Immutable @Table(name = "effective_vault_access") -@NamedQuery(name = "EffectiveVaultAccess.countSeatsOccupiedByUser", query = """ +@NamedQuery(name = "EffectiveVaultAccess.countSeatsOccupiedBySingleUser", query = """ SELECT count(u) FROM User u INNER JOIN EffectiveVaultAccess eva ON u.id = eva.id.authorityId @@ -62,49 +56,55 @@ SELECT count(DISTINCT u) FROM EffectiveVaultAccess eva WHERE eva.id.vaultId = :vaultId AND eva.id.authorityId = :authorityId """) -public class EffectiveVaultAccess extends PanacheEntityBase { +public class EffectiveVaultAccess { @EmbeddedId - public EffectiveVaultAccess.Id id; + EffectiveVaultAccess.Id id; - public static boolean isUserOccupyingSeat(String userId) { - return EffectiveVaultAccess.count("#EffectiveVaultAccess.countSeatsOccupiedByUser", Parameters.with("userId", userId)) > 0; + public Id getId() { + return id; } - public static long countSeatsOccupiedByUsers(List userIds) { - return EffectiveVaultAccess.count("#EffectiveVaultAccess.countSeatsOccupiedByUsers", Parameters.with("userIds", userIds)); - } - - public static long countSeatOccupyingUsers() { - return EffectiveVaultAccess.count("#EffectiveVaultAccess.countSeatOccupyingUsers"); - } - - public static long countSeatOccupyingUsersWithAccessToken() { - return EffectiveVaultAccess.count("#EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken"); - } - - public static long countSeatOccupyingUsersOfGroup(String groupId) { - return EffectiveVaultAccess.count("#EffectiveVaultAccess.countSeatOccupyingUsersOfGroup", Parameters.with("groupId", groupId)); - } - - public static Collection listRoles(UUID vaultId, String authorityId) { - return EffectiveVaultAccess.find("#EffectiveVaultAccess.findByAuthorityAndVault", Parameters.with("vaultId", vaultId).and("authorityId", authorityId)).stream() - .map(eva -> eva.id.role) - .collect(Collectors.toUnmodifiableSet()); + public void setId(Id id) { + this.id = id; } @Embeddable public static class Id implements Serializable { @Column(name = "vault_id") - public UUID vaultId; + UUID vaultId; @Column(name = "authority_id") - public String authorityId; + String authorityId; @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) - public VaultAccess.Role role; + VaultAccess.Role role; + + public UUID getVaultId() { + return vaultId; + } + + public void setVaultId(UUID vaultId) { + this.vaultId = vaultId; + } + + public String getAuthorityId() { + return authorityId; + } + + public void setAuthorityId(String authorityId) { + this.authorityId = authorityId; + } + + public VaultAccess.Role getRole() { + return role; + } + + public void setRole(VaultAccess.Role role) { + this.role = role; + } public Id(UUID vaultId, String authorityId, VaultAccess.Role role) { this.vaultId = vaultId; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccessRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccessRepository.java new file mode 100644 index 000000000..e82037aa3 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccessRepository.java @@ -0,0 +1,41 @@ +package org.cryptomator.hub.entities; + +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import io.quarkus.panache.common.Parameters; +import jakarta.enterprise.context.ApplicationScoped; + +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@ApplicationScoped +public class EffectiveVaultAccessRepository implements PanacheRepositoryBase { + + + public boolean isUserOccupyingSeat(String userId) { + return count("#EffectiveVaultAccess.countSeatsOccupiedBySingleUser", Parameters.with("userId", userId)) > 0; + } + + public long countSeatsOccupiedByUsers(List userIds) { + return count("#EffectiveVaultAccess.countSeatsOccupiedByUsers", Parameters.with("userIds", userIds)); + } + + public long countSeatOccupyingUsers() { + return count("#EffectiveVaultAccess.countSeatOccupyingUsers"); + } + + public long countSeatOccupyingUsersWithAccessToken() { + return count("#EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken"); + } + + public long countSeatOccupyingUsersOfGroup(String groupId) { + return count("#EffectiveVaultAccess.countSeatOccupyingUsersOfGroup", Parameters.with("groupId", groupId)); + } + + public Collection listRoles(UUID vaultId, String authorityId) { + return find("#EffectiveVaultAccess.findByAuthorityAndVault", Parameters.with("vaultId", vaultId).and("authorityId", authorityId)).stream() + .map(eva -> eva.getId().getRole()) + .collect(Collectors.toUnmodifiableSet()); + } +} 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 6e8c3b759..6e3084988 100644 --- a/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java +++ b/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java @@ -10,6 +10,7 @@ import jakarta.ws.rs.core.Context; import jakarta.ws.rs.ext.Provider; import org.cryptomator.hub.entities.EffectiveVaultAccess; +import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.VaultAccess; import org.eclipse.microprofile.jwt.JsonWebToken; @@ -30,6 +31,9 @@ public class VaultRoleFilter implements ContainerRequestFilter { @Inject JsonWebToken jwt; + @Inject + EffectiveVaultAccessRepository effectiveVaultAccessRepo; + @Context ResourceInfo resourceInfo; @@ -52,7 +56,7 @@ public void filter(ContainerRequestContext requestContext) throws NotFoundExcept var forbiddenMsg = "Vault role required: " + Arrays.stream(annotation.value()).map(VaultAccess.Role::name).collect(Collectors.joining(", ")); if (Vault.findByIdOptional(vaultId).isPresent()) { // check permissions for existing vault: - var effectiveRoles = EffectiveVaultAccess.listRoles(vaultId, userId); + var effectiveRoles = effectiveVaultAccessRepo.listRoles(vaultId, userId); if (Arrays.stream(annotation.value()).noneMatch(effectiveRoles::contains)) { throw new ForbiddenException(forbiddenMsg); } diff --git a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java index ed5e973d7..c85bea156 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java +++ b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java @@ -12,7 +12,7 @@ import jakarta.inject.Inject; import jakarta.transaction.Transactional; import jakarta.validation.Validator; -import org.cryptomator.hub.entities.EffectiveVaultAccess; +import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; import org.cryptomator.hub.entities.Vault; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; @@ -59,6 +59,8 @@ public class VaultResourceIT { @Inject AgroalDataSource dataSource; + @Inject + EffectiveVaultAccessRepository effectiveVaultAccessRepo; @Inject Validator validator; @@ -695,7 +697,7 @@ public void setup() throws SQLException { @Order(0) @DisplayName("POST /vaults/7E57C0DE-0000-4000-8000-000100001111/access-tokens returns 402 for [user91, user92, user93, user94]") public void grantAccessExceedingSeats() { - assert EffectiveVaultAccess.countSeatOccupyingUsers() == 2; + assert effectiveVaultAccessRepo.countSeatOccupyingUsers() == 2; var body = Map.of( "user91", "jwe.jwe.jwe.vault1.user91", // "user92", "jwe.jwe.jwe.vault1.user92", // @@ -712,7 +714,7 @@ public void grantAccessExceedingSeats() { @Order(1) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/groups/group91 returns 402") public void addGroupToVaultExceedingSeats() { - assert EffectiveVaultAccess.countSeatOccupyingUsers() == 2; + assert effectiveVaultAccessRepo.countSeatOccupyingUsers() == 2; given().when().put("/vaults/{vaultId}/groups/{groupId}", "7E57C0DE-0000-4000-8000-000100001111", "group91") .then().statusCode(402); @@ -723,7 +725,7 @@ public void addGroupToVaultExceedingSeats() { @ParameterizedTest(name = "Adding user {0} succeeds") @CsvSource(value = {"0,user91", "1,user92", "2,user93"}) public void addUserToVaultNotExceedingSeats(String run, String userId) { - assert EffectiveVaultAccess.countSeatOccupyingUsers() == (2 + Integer.valueOf(run)); + assert effectiveVaultAccessRepo.countSeatOccupyingUsers() == (2 + Integer.valueOf(run)); given().when().put("/vaults/{vaultId}/users/{usersId}", "7E57C0DE-0000-4000-8000-000100001111", userId) .then().statusCode(201); @@ -733,7 +735,7 @@ public void addUserToVaultNotExceedingSeats(String run, String userId) { @Order(3) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/users/user94 returns 402") public void addUserToVaultExceedingSeats() { - assert EffectiveVaultAccess.countSeatOccupyingUsers() == 5; + assert effectiveVaultAccessRepo.countSeatOccupyingUsers() == 5; given().when().put("/vaults/{vaultId}/users/{usersId}", "7E57C0DE-0000-4000-8000-000100001111", "user94") .then().statusCode(402); @@ -743,7 +745,7 @@ public void addUserToVaultExceedingSeats() { @Order(4) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111 (as user1) returns 200 with only updated name, description and archive flag, despite exceeding license") public void testUpdateVaultDespiteLicenseExceeded() { - assert EffectiveVaultAccess.countSeatOccupyingUsers() == 5; + assert effectiveVaultAccessRepo.countSeatOccupyingUsers() == 5; var vaultId = "7E57C0DE-0000-4000-8000-000100001111"; var vaultDto = new VaultResource.VaultDto(UUID.fromString(vaultId), "Vault 1", "This is a testvault.", false, Instant.parse("2222-11-11T11:11:11Z"), "someVaule", -1, "doNotUpdate", "doNotUpdate", "doNotUpdate"); @@ -769,7 +771,7 @@ public void testCreateVaultExceedingSeats() throws SQLException { """); } - assert EffectiveVaultAccess.countSeatOccupyingUsers() > 5; + assert effectiveVaultAccessRepo.countSeatOccupyingUsers() > 5; var uuid = UUID.fromString("7E57C0DE-0000-4000-8000-0001FFFF3333"); var vaultDto = new VaultResource.VaultDto(uuid, "My Vault", "Test vault 4", false, Instant.parse("2112-12-21T21:12:21Z"), "masterkey3", 42, "NaCl", "authPubKey3", "authPrvKey3"); @@ -782,7 +784,7 @@ public void testCreateVaultExceedingSeats() throws SQLException { @Order(7) @DisplayName("unlock/legacyUnlock is granted, if (effective vault user) > license seats but (effective vault user with access token) <= license seat") public void testUnlockAllowedExceedingLicenseSoftLimit() throws SQLException { - assert EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken() <= 5; + assert effectiveVaultAccessRepo.countSeatOccupyingUsersWithAccessToken() <= 5; when().get("/vaults/{vaultId}/access-token", "7E57C0DE-0000-4000-8000-000100001111") .then().statusCode(200); @@ -807,7 +809,7 @@ public void testUnockBlockedExceedingLicenseHardLimit() throws SQLException { VALUES ('user94', '7E57C0DE-0000-4000-8000-000100001111', 'jwe.jwe.jwe.vault1.user94'); """); } - assert EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken() > 5; + assert effectiveVaultAccessRepo.countSeatOccupyingUsersWithAccessToken() > 5; when().get("/vaults/{vaultId}/access-token", "7E57C0DE-0000-4000-8000-000100001111") .then().statusCode(402); 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 a190985b6..087e77fa3 100644 --- a/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java +++ b/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java @@ -10,6 +10,7 @@ import jakarta.ws.rs.core.MultivaluedHashMap; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.UriInfo; +import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; import org.cryptomator.hub.entities.VaultAccess; import org.eclipse.microprofile.jwt.JsonWebToken; import org.junit.jupiter.api.Assertions; @@ -21,6 +22,7 @@ import java.lang.reflect.Method; import java.util.Map; +import java.util.Set; @QuarkusTest public class VaultRoleFilterTest { @@ -29,12 +31,14 @@ public class VaultRoleFilterTest { private final UriInfo uriInfo = Mockito.mock(UriInfo.class); private final ContainerRequestContext context = Mockito.mock(ContainerRequestContext.class); private final JsonWebToken jwt = Mockito.mock(JsonWebToken.class); + private final EffectiveVaultAccessRepository effectiveVaultAccessRepo = Mockito.mock(EffectiveVaultAccessRepository.class); private final VaultRoleFilter filter = new VaultRoleFilter(); @BeforeEach public void setup() { filter.resourceInfo = resourceInfo; filter.jwt = jwt; + filter.effectiveVaultAccessRepo = effectiveVaultAccessRepo; Mockito.doReturn(uriInfo).when(context).getUriInfo(); } @@ -63,6 +67,7 @@ public void testFilterWithInsufficientPrivileges() throws NoSuchMethodException Mockito.doReturn(VaultRoleFilterTest.class.getMethod("allowOwner")).when(resourceInfo).getResourceMethod(); Mockito.doReturn(new MultivaluedHashMap<>(Map.of(VaultRole.DEFAULT_VAULT_ID_PARAM, "7E57C0DE-0000-4000-8000-000100001111"))).when(uriInfo).getPathParameters(); Mockito.doReturn("user2").when(jwt).getSubject(); + Mockito.when(effectiveVaultAccessRepo.listRoles(Mockito.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-000100001111")),Mockito.eq("user2"))).thenReturn(Set.of()); var e = Assertions.assertThrows(ForbiddenException.class, () -> filter.filter(context)); @@ -75,6 +80,7 @@ public void testFilterSuccess1() throws NoSuchMethodException { Mockito.doReturn(VaultRoleFilterTest.class.getMethod("allowOwner")).when(resourceInfo).getResourceMethod(); Mockito.doReturn(new MultivaluedHashMap<>(Map.of(VaultRole.DEFAULT_VAULT_ID_PARAM, "7E57C0DE-0000-4000-8000-000100001111"))).when(uriInfo).getPathParameters(); Mockito.doReturn("user1").when(jwt).getSubject(); + Mockito.when(effectiveVaultAccessRepo.listRoles(Mockito.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-000100001111")),Mockito.eq("user1"))).thenReturn(Set.of(VaultAccess.Role.OWNER)); Assertions.assertDoesNotThrow(() -> filter.filter(context)); } @@ -85,6 +91,7 @@ public void testFilterSuccess2() throws NoSuchMethodException { Mockito.doReturn(VaultRoleFilterTest.class.getMethod("allowOwner")).when(resourceInfo).getResourceMethod(); Mockito.doReturn(new MultivaluedHashMap<>(Map.of(VaultRole.DEFAULT_VAULT_ID_PARAM, "7E57C0DE-0000-4000-8000-000100002222"))).when(uriInfo).getPathParameters(); Mockito.doReturn("user2").when(jwt).getSubject(); + Mockito.when(effectiveVaultAccessRepo.listRoles(Mockito.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-000100002222")),Mockito.eq("user2"))).thenReturn(Set.of(VaultAccess.Role.OWNER)); Assertions.assertDoesNotThrow(() -> filter.filter(context)); } @@ -103,6 +110,7 @@ public void setup() { public void testFilterSuccess() throws NoSuchMethodException { Mockito.doReturn(VaultRoleFilterTest.class.getMethod("allowOwner")).when(resourceInfo).getResourceMethod(); Mockito.doReturn("user1").when(jwt).getSubject(); + Mockito.when(effectiveVaultAccessRepo.listRoles(Mockito.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-00010000AAAA")),Mockito.eq("user1"))).thenReturn(Set.of(VaultAccess.Role.OWNER)); Assertions.assertDoesNotThrow(() -> filter.filter(context)); } From 1bb99fce50aff8f670be6ba495e296fbb9e5ae6f Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 20 Mar 2024 16:11:00 +0100 Subject: [PATCH 32/54] migrate LegacyAccessToken to repository pattern --- .../cryptomator/hub/api/DeviceResource.java | 8 ++- .../cryptomator/hub/api/VaultResource.java | 7 ++- .../hub/entities/LegacyAccessToken.java | 55 ++++++++++++------- .../entities/LegacyAccessTokenRepository.java | 23 ++++++++ 4 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessTokenRepository.java 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 a13feface..b351466ae 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java @@ -23,6 +23,7 @@ import org.cryptomator.hub.entities.Device; import org.cryptomator.hub.entities.DeviceRepository; import org.cryptomator.hub.entities.LegacyAccessToken; +import org.cryptomator.hub.entities.LegacyAccessTokenRepository; import org.cryptomator.hub.entities.LegacyDevice; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.UserRepository; @@ -60,6 +61,9 @@ public class DeviceResource { UserRepository userRepo; @Inject DeviceRepository deviceRepo; + @Inject + LegacyAccessTokenRepository legacyAccessTokenRepo; + @Inject JsonWebToken jwt; @@ -140,8 +144,8 @@ public DeviceDto get(@PathParam("deviceId") @ValidId String deviceId) { @Operation(summary = "list legacy access tokens", description = "get all legacy access tokens for this device ({vault1: token1, vault1: token2, ...}). The device must be owned by the currently logged-in user") @APIResponse(responseCode = "200") public Map getLegacyAccessTokens(@PathParam("deviceId") @ValidId String deviceId) { - return LegacyAccessToken.getByDeviceAndOwner(deviceId, jwt.getSubject()) - .collect(Collectors.toMap(token -> token.id.vaultId, token -> token.jwe)); + return legacyAccessTokenRepo.getByDeviceAndOwner(deviceId, jwt.getSubject()) + .collect(Collectors.toMap(token -> token.getId().getVaultId(), LegacyAccessToken::getJwe)); } @DELETE 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 bcae223a7..6cdf4e0df 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -37,6 +37,7 @@ import org.cryptomator.hub.entities.Group; import org.cryptomator.hub.entities.GroupRepository; import org.cryptomator.hub.entities.LegacyAccessToken; +import org.cryptomator.hub.entities.LegacyAccessTokenRepository; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.UserRepository; import org.cryptomator.hub.entities.Vault; @@ -99,6 +100,8 @@ public class VaultResource { UserRepository userRepo; @Inject EffectiveVaultAccessRepository effectiveVaultAccessRepo; + @Inject + LegacyAccessTokenRepository legacyAccessTokenRepo; @Inject JsonWebToken jwt; @@ -294,12 +297,12 @@ public Response legacyUnlock(@PathParam("vaultId") UUID vaultId, @PathParam("dev throw new PaymentRequiredException("Number of effective vault users exceeds available license seats"); } - var access = LegacyAccessToken.unlock(vaultId, deviceId, jwt.getSubject()); + var access = legacyAccessTokenRepo.unlock(vaultId, deviceId, jwt.getSubject()); if (access != null) { vaultKeyRetrievedEventRepo.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); var subscriptionStateHeaderName = "Hub-Subscription-State"; var subscriptionStateHeaderValue = license.isSet() ? "ACTIVE" : "INACTIVE"; // license expiration is not checked here, because it is checked in the ActiveLicense filter - return Response.ok(access.jwe).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); + return Response.ok(access.getJwe()).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); } else { vaultKeyRetrievedEventRepo.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); throw new ForbiddenException("Access to this device not granted."); diff --git a/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java b/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java index 6a8f826a0..f74410d44 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java @@ -31,31 +31,28 @@ WHERE token.id.deviceId = :deviceId AND device.ownerId = :userId """) @Deprecated -public class LegacyAccessToken extends PanacheEntityBase { +public class LegacyAccessToken { @EmbeddedId - public AccessId id = new AccessId(); + AccessId id = new AccessId(); @Column(name = "jwe", nullable = false) - public String jwe; - - public static LegacyAccessToken unlock(UUID vaultId, String deviceId, String userId) { - try { - return getEntityManager().createNamedQuery("LegacyAccessToken.get", LegacyAccessToken.class) // - .setParameter("deviceId", deviceId) // - .setParameter("vaultId", vaultId) // - .setParameter("userId", userId) // - .getSingleResult(); - } catch (NoResultException e) { - return null; - } + String jwe; + + public AccessId getId() { + return id; + } + + public void setId(AccessId id) { + this.id = id; } - public static Stream getByDeviceAndOwner(String deviceId, String userId) { - return getEntityManager().createNamedQuery("LegacyAccessToken.getByDevice", LegacyAccessToken.class) // - .setParameter("deviceId", deviceId) // - .setParameter("userId", userId) // - .getResultStream(); + public String getJwe() { + return jwe; + } + + public void setJwe(String jwe) { + this.jwe = jwe; } @Override @@ -84,10 +81,26 @@ public String toString() { public static class AccessId implements Serializable { @Column(name = "device_id", nullable = false) - public String deviceId; + String deviceId; @Column(name = "vault_id", nullable = false) - public UUID vaultId; + UUID vaultId; + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public UUID getVaultId() { + return vaultId; + } + + public void setVaultId(UUID vaultId) { + this.vaultId = vaultId; + } public AccessId(String deviceId, UUID vaultId) { this.deviceId = deviceId; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessTokenRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessTokenRepository.java new file mode 100644 index 000000000..a245a5418 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessTokenRepository.java @@ -0,0 +1,23 @@ +package org.cryptomator.hub.entities; + +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import jakarta.enterprise.context.ApplicationScoped; + +import java.util.Map; +import java.util.UUID; +import java.util.stream.Stream; + +@ApplicationScoped +@Deprecated +public class LegacyAccessTokenRepository implements PanacheRepositoryBase { + + public LegacyAccessToken unlock(UUID vaultId, String deviceId, String userId) { + return find("#LegacyAccessToken.get", Map.of("deviceId", deviceId, "vaultId", vaultId, "userId", userId)) + .firstResult(); + } + + public Stream getByDeviceAndOwner(String deviceId, String userId) { + return stream("#LegacyAccessToken.getByDevice", Map.of("deviceId", deviceId, "userId", userId)); + } + +} From 2a6aa47b31e72e119b7d0fc65aa68fae8f02bab5 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 20 Mar 2024 16:16:22 +0100 Subject: [PATCH 33/54] migrate LegacyDevice to repository pattern --- .../cryptomator/hub/api/DeviceResource.java | 6 +++-- .../hub/api/LegacyDeviceRepository.java | 10 +++++++++ .../hub/entities/LegacyDevice.java | 22 ++++++++++++++++--- 3 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/api/LegacyDeviceRepository.java 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 b351466ae..294cb050e 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java @@ -63,6 +63,8 @@ public class DeviceResource { DeviceRepository deviceRepo; @Inject LegacyAccessTokenRepository legacyAccessTokenRepo; + @Inject + LegacyDeviceRepository legacyDeviceRepo; @Inject JsonWebToken jwt; @@ -98,8 +100,8 @@ public Response createOrUpdate(@Valid @NotNull DeviceDto dto, @PathParam("device device.setCreationTime(Instant.now().truncatedTo(ChronoUnit.MILLIS)); device.setType(dto.type != null ? dto.type : Device.Type.DESKTOP); // default to desktop for backwards compatibilit); - if (LegacyDevice.deleteById(device.getId())) { - assert LegacyDevice.findById(device.getId()) == null; + if (legacyDeviceRepo.deleteById(device.getId())) { + assert legacyDeviceRepo.findById(device.getId()) == null; LOG.info("Deleted Legacy Device during re-registration of Device " + deviceId); } } diff --git a/backend/src/main/java/org/cryptomator/hub/api/LegacyDeviceRepository.java b/backend/src/main/java/org/cryptomator/hub/api/LegacyDeviceRepository.java new file mode 100644 index 000000000..bf2820d01 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/api/LegacyDeviceRepository.java @@ -0,0 +1,10 @@ +package org.cryptomator.hub.api; + +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import jakarta.enterprise.context.ApplicationScoped; +import org.cryptomator.hub.entities.LegacyDevice; + +@ApplicationScoped +@Deprecated +public class LegacyDeviceRepository implements PanacheRepositoryBase { +} 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 160b7907a..8369b172c 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/LegacyDevice.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/LegacyDevice.java @@ -9,14 +9,30 @@ @Deprecated @Entity @Table(name = "device_legacy") -public class LegacyDevice extends PanacheEntityBase { +public class LegacyDevice { @Id @Column(name = "id", nullable = false) - public String id; + String id; @Column(name = "owner_id", nullable = false) - public String ownerId; + String ownerId; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getOwnerId() { + return ownerId; + } + + public void setOwnerId(String ownerId) { + this.ownerId = ownerId; + } // Further attributes omitted, as they are no longer used. The above ones are exceptions, as they are referenced via JPQL for joining. From 689d9ff4a5fbc520b09aa44a59ae2658fe6da07c Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 20 Mar 2024 16:17:08 +0100 Subject: [PATCH 34/54] wrong package --- .../src/main/java/org/cryptomator/hub/api/DeviceResource.java | 2 +- .../hub/{api => entities}/LegacyDeviceRepository.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename backend/src/main/java/org/cryptomator/hub/{api => entities}/LegacyDeviceRepository.java (88%) 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 294cb050e..1430827a5 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java @@ -24,7 +24,7 @@ import org.cryptomator.hub.entities.DeviceRepository; import org.cryptomator.hub.entities.LegacyAccessToken; import org.cryptomator.hub.entities.LegacyAccessTokenRepository; -import org.cryptomator.hub.entities.LegacyDevice; +import org.cryptomator.hub.entities.LegacyDeviceRepository; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.UserRepository; import org.cryptomator.hub.entities.events.DeviceRegisteredEventRepository; diff --git a/backend/src/main/java/org/cryptomator/hub/api/LegacyDeviceRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/LegacyDeviceRepository.java similarity index 88% rename from backend/src/main/java/org/cryptomator/hub/api/LegacyDeviceRepository.java rename to backend/src/main/java/org/cryptomator/hub/entities/LegacyDeviceRepository.java index bf2820d01..80841b8a2 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/LegacyDeviceRepository.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/LegacyDeviceRepository.java @@ -1,4 +1,4 @@ -package org.cryptomator.hub.api; +package org.cryptomator.hub.entities; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; import jakarta.enterprise.context.ApplicationScoped; From e465ffd90cced177e1fd4da5732f2bb3e2e7f5ec Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 21 Mar 2024 16:24:05 +0100 Subject: [PATCH 35/54] migrate settings enitity to panache repository refactor also licenseHolder including tests --- .../cryptomator/hub/api/BillingResource.java | 8 +- .../cryptomator/hub/entities/Settings.java | 38 +- .../hub/entities/SettingsRepository.java | 16 + .../hub/license/LicenseHolder.java | 148 +++---- .../hub/license/LicenseValidator.java | 2 +- .../hub/api/BillingResourceIT.java | 3 +- .../hub/license/InitialLicenseIT.java | 171 -------- .../hub/license/LicenseHolderIT.java | 390 ----------------- .../hub/license/LicenseHolderTest.java | 403 ++++++++++++++++++ 9 files changed, 531 insertions(+), 648 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/SettingsRepository.java delete mode 100644 backend/src/test/java/org/cryptomator/hub/license/InitialLicenseIT.java delete mode 100644 backend/src/test/java/org/cryptomator/hub/license/LicenseHolderIT.java create mode 100644 backend/src/test/java/org/cryptomator/hub/license/LicenseHolderTest.java 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 32939d9c0..e2e5e80e4 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,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; -import org.cryptomator.hub.entities.Settings; +import org.cryptomator.hub.entities.SettingsRepository; import org.cryptomator.hub.license.LicenseHolder; import org.cryptomator.hub.validation.ValidJWS; import org.eclipse.microprofile.openapi.annotations.Operation; @@ -31,6 +31,8 @@ public class BillingResource { LicenseHolder licenseHolder; @Inject EffectiveVaultAccessRepository effectiveVaultAccessRepo; + @Inject + SettingsRepository settingsRepo; @GET @Path("/") @@ -46,8 +48,8 @@ public BillingDto get() { return Optional.ofNullable(licenseHolder.get()) .map(jwt -> BillingDto.fromDecodedJwt(jwt, usedSeats, isManaged)) .orElseGet(() -> { - var hubId = Settings.get().hubId; - return BillingDto.create(hubId, (int) licenseHolder.getNoLicenseSeats(), usedSeats, isManaged); + var hubId = settingsRepo.get().getHubId(); + return BillingDto.create(hubId, (int) licenseHolder.getSeats(), usedSeats, isManaged); }); } 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 233309826..e2eaf8545 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Settings.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Settings.java @@ -1,6 +1,5 @@ package org.cryptomator.hub.entities; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -10,19 +9,43 @@ @Entity @Table(name = "settings") -public class Settings extends PanacheEntityBase { +public class Settings { - private static final int SINGLETON_ID = 0; + static final int SINGLETON_ID = 0; @Id @Column(name = "id", nullable = false, updatable = false) - public int id; + int id; @Column(name = "hub_id", nullable = false) - public String hubId; + String hubId; @Column(name = "license_key") - public String licenseKey; + String licenseKey; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getHubId() { + return hubId; + } + + public void setHubId(String hubId) { + this.hubId = hubId; + } + + public String getLicenseKey() { + return licenseKey; + } + + public void setLicenseKey(String licenseKey) { + this.licenseKey = licenseKey; + } @Override public String toString() { @@ -48,8 +71,5 @@ public int hashCode() { return Objects.hash(id, hubId, licenseKey); } - public static Settings get() { - return Objects.requireNonNull(Settings.findById(SINGLETON_ID), "Settings not initialized"); - } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/SettingsRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/SettingsRepository.java new file mode 100644 index 000000000..7fbbaf327 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/SettingsRepository.java @@ -0,0 +1,16 @@ +package org.cryptomator.hub.entities; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +import java.util.Objects; + +import static org.cryptomator.hub.entities.Settings.SINGLETON_ID; + +@ApplicationScoped +public class SettingsRepository implements PanacheRepository { + + public Settings get() { + return Objects.requireNonNull(findById((long) SINGLETON_ID), "Settings not initialized"); + } +} 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 413f0758f..d32d7b6d0 100644 --- a/backend/src/main/java/org/cryptomator/hub/license/LicenseHolder.java +++ b/backend/src/main/java/org/cryptomator/hub/license/LicenseHolder.java @@ -4,11 +4,14 @@ import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import io.quarkus.scheduler.Scheduled; +import io.quarkus.scheduler.ScheduledExecution; +import io.smallrye.common.annotation.NonBlocking; import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import org.cryptomator.hub.entities.Settings; +import org.cryptomator.hub.entities.SettingsRepository; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; @@ -20,13 +23,14 @@ import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; @ApplicationScoped -public class LicenseHolder { +public class LicenseHolder implements Scheduled.SkipPredicate { + + private static final int SELFHOSTED_NOLICENSE_SEATS = 5; + private static final int MANAGED_NOLICENSE_SEATS = 0; @Inject @ConfigProperty(name = "hub.managed-instance", defaultValue = "false") @@ -38,13 +42,15 @@ public class LicenseHolder { @Inject @ConfigProperty(name = "hub.initial-license") - Optional initialLicense; + Optional initialLicenseToken; @Inject LicenseValidator licenseValidator; @Inject RandomMinuteSleeper randomMinuteSleeper; + @Inject + SettingsRepository settingsRepo; private static final Logger LOG = Logger.getLogger(LicenseHolder.class); private DecodedJWT license; @@ -54,36 +60,35 @@ public class LicenseHolder { */ @PostConstruct void init() { - var settings = Settings.get(); - if (settings.licenseKey != null) { - validateLicense(settings.licenseKey, settings.hubId); - } else if (initialId.isPresent() && initialLicense.isPresent()) { - applyInitialHubIdAndLicense(initialId.get(), initialLicense.get()); + var settings = settingsRepo.get(); + if (settings.getLicenseKey() != null && settings.getHubId() != null) { + validateOrResetExistingLicense(settings); + } else if (initialLicenseToken.isPresent() && initialId.isPresent()) { + validateAndApplyInitLicense(settings, initialLicenseToken.get(), initialId.get() ); } } @Transactional - void validateLicense(String licenseKey, String hubId) { + void validateOrResetExistingLicense(Settings settings) { try { - this.license = licenseValidator.validate(licenseKey, hubId); + this.license = licenseValidator.validate(settings.getLicenseKey(), settings.getHubId()); } catch (JWTVerificationException e) { - LOG.warn("Provided license is invalid. Deleting entry. Please add the license over the REST API again."); - var settings = Settings.get(); - settings.licenseKey = null; - settings.persistAndFlush(); + LOG.warn("License in database is invalid or does not match hubId", e); + LOG.warn("Deleting license entry. Please add the license over the REST API again."); + settings.setLicenseKey(null); + settingsRepo.persistAndFlush(settings); } } @Transactional - void applyInitialHubIdAndLicense(String initialId, String initialLicense) { + void validateAndApplyInitLicense(Settings settings, String initialLicenseToken, String initialHubId) { try { - this.license = licenseValidator.validate(initialLicense, initialId); - var settings = Settings.get(); - settings.licenseKey = initialLicense; - settings.hubId = initialId; - settings.persistAndFlush(); + this.license = licenseValidator.validate(initialLicenseToken, initialHubId); + settings.setLicenseKey(initialLicenseToken); + settings.setHubId(initialHubId); + settingsRepo.persistAndFlush(settings); } catch (JWTVerificationException e) { - LOG.warn("Provided initial license is invalid."); + LOG.warn("Provided initial license is invalid or does not match inital hubId.", e); } } @@ -95,54 +100,60 @@ void applyInitialHubIdAndLicense(String initialId, String initialLicense) { */ @Transactional public void set(String token) throws JWTVerificationException { - Objects.requireNonNull(token); - - var settings = Settings.get(); - this.license = licenseValidator.validate(token, settings.hubId); - settings.licenseKey = token; - settings.persistAndFlush(); + var settings = settingsRepo.get(); + this.license = licenseValidator.validate(token, settings.getHubId()); + settings.setLicenseKey(token); + settingsRepo.persistAndFlush(settings); } /** * Attempts to refresh the Hub licence every day between 01:00:00 and 02:00:00 AM UTC if claim refreshURL is present. */ - @Scheduled(cron = "0 0 1 * * ?", timeZone = "UTC", concurrentExecution = Scheduled.ConcurrentExecution.SKIP) - void refreshLicenseScheduler() throws InterruptedException { - if (license != null) { - randomMinuteSleeper.sleep(); // add random sleep between [0,59]min to reduce infrastructure load - var refreshUrl = licenseValidator.refreshUrl(license.getToken()); - if (refreshUrl.isPresent()) { - var client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build(); - refreshLicense(refreshUrl.get(), license.getToken(), client); + @Scheduled(cron = "0 0 1 * * ?", timeZone = "UTC", concurrentExecution = Scheduled.ConcurrentExecution.SKIP, skipExecutionIf = LicenseHolder.class) + @NonBlocking + void refreshLicense() throws InterruptedException { + randomMinuteSleeper.sleep(); // add random sleep between [0,59]min to reduce infrastructure load + var refreshUrlClaim = get().getClaim("refreshUrl"); + if (refreshUrlClaim != null) { + try { + var refreshUrl = URI.create(refreshUrlClaim.asString()); + var refreshedLicense = requestLicenseRefresh(refreshUrl, get().getToken()); + set(refreshedLicense); + } catch (LicenseRefreshFailedException lrfe) { + LOG.errorv("Failed to refresh license token. Request to {0} was answerd with response code {1,number,integer}", refreshUrlClaim, lrfe.statusCode); + } catch (IllegalArgumentException | IOException e) { + LOG.error("Failed to refresh license token", e); + } catch (JWTVerificationException jve) { + LOG.error("Failed to refresh license token. Refreshed token is invalid.", jve); } } } //visible for testing - void refreshLicense(String refreshUrl, String license, HttpClient client) throws InterruptedException { - var parameters = Map.of("token", license); - var body = parameters.entrySet() // - .stream() // - .map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8)) // - .collect(Collectors.joining("&")); - var request = HttpRequest.newBuilder() // - .uri(URI.create(refreshUrl)) // - .headers("Content-Type", "application/x-www-form-urlencoded") // - .POST(HttpRequest.BodyPublishers.ofString(body)) // - .version(HttpClient.Version.HTTP_1_1) // - .build(); - try { + String requestLicenseRefresh(URI refreshUrl, String licenseToken) throws InterruptedException, IOException, LicenseRefreshFailedException { + try (var client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build()) { + var body = "token=" + URLEncoder.encode(licenseToken, StandardCharsets.UTF_8); + var request = HttpRequest.newBuilder() // + .uri(refreshUrl) // + .headers("Content-Type", "application/x-www-form-urlencoded") // + .POST(HttpRequest.BodyPublishers.ofString(body)) // + .version(HttpClient.Version.HTTP_1_1) // + .build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 200 && !response.body().isEmpty()) { - set(response.body()); + return response.body(); } else { - LOG.error("Failed to refresh license token with response code: " + response.statusCode()); + throw new LicenseRefreshFailedException(response.statusCode(), body); } - } catch (IOException | JWTVerificationException e) { - LOG.error("Failed to refresh license token", e); } } + //necessary for skipExecutionIf + @Override + public boolean test(ScheduledExecution execution) { + return license == null; + } + public DecodedJWT get() { return license; } @@ -150,7 +161,7 @@ public DecodedJWT get() { /** * Checks if the license is set. * - * @return {@code true}, if the license _is set_. Otherwise false. + * @return {@code true}, if the license _is not null_. Otherwise false. */ public boolean isSet() { return license != null; @@ -159,7 +170,7 @@ public boolean isSet() { /** * Checks if the license is expired. * - * @return {@code true}, if the license _is set and expired_. Otherwise false. + * @return {@code true}, if the license _is not nul and expired_. Otherwise false. */ public boolean isExpired() { return Optional.ofNullable(license) // @@ -170,7 +181,7 @@ public boolean isExpired() { /** * Gets the number of seats in the license * - * @return Number of seats of the license, if license is not null. Otherwise {@value SelfHostedNoLicenseConstants#SEATS}. + * @return Number of seats of the license, if license is not null. Otherwise {@value SELFHOSTED_NOLICENSE_SEATS}. */ public long getSeats() { return Optional.ofNullable(license) // @@ -179,11 +190,11 @@ public long getSeats() { .orElseGet(this::getNoLicenseSeats); } - public long getNoLicenseSeats() { + private long getNoLicenseSeats() { if (!managedInstance) { - return SelfHostedNoLicenseConstants.SEATS; + return SELFHOSTED_NOLICENSE_SEATS; } else { - return ManagedInstanceNoLicenseConstants.SEATS; + return MANAGED_NOLICENSE_SEATS; } } @@ -191,20 +202,13 @@ public boolean isManagedInstance() { return managedInstance; } - public static class SelfHostedNoLicenseConstants { - public static final long SEATS = 5; + static class LicenseRefreshFailedException extends RuntimeException { + final int statusCode; + final String body; - private SelfHostedNoLicenseConstants() { - throw new IllegalStateException("Utility class"); + LicenseRefreshFailedException(int statusCode, String body) { + this.statusCode = statusCode; + this.body = body; } } - - public static class ManagedInstanceNoLicenseConstants { - public static final long SEATS = 0; - - private ManagedInstanceNoLicenseConstants() { - throw new IllegalStateException("Utility class"); - } - } - } 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 ff19aba58..2cb600622 100644 --- a/backend/src/main/java/org/cryptomator/hub/license/LicenseValidator.java +++ b/backend/src/main/java/org/cryptomator/hub/license/LicenseValidator.java @@ -58,7 +58,7 @@ private static ECPublicKey decodePublicKey(String pemEncodedPublicKey) { public DecodedJWT validate(String token, String expectedHubId) throws JWTVerificationException { var jwt = verifier.verify(token); if (!jwt.getId().equals(expectedHubId)) { - throw new InvalidClaimException("Token ID does not match your Hub ID."); + throw new InvalidClaimException("Token ID " + jwt.getId() + " does not match your Hub ID " + expectedHubId); } for (var claim : REQUIRED_CLAIMS) { if (Objects.isNull(jwt.getClaim(claim))) { diff --git a/backend/src/test/java/org/cryptomator/hub/api/BillingResourceIT.java b/backend/src/test/java/org/cryptomator/hub/api/BillingResourceIT.java index 20dcbe790..da47f9ffe 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/BillingResourceIT.java +++ b/backend/src/test/java/org/cryptomator/hub/api/BillingResourceIT.java @@ -55,8 +55,7 @@ public class AsAdmin { @DisplayName("GET /billing returns 200 with empty license self-hosted") public void testGetEmptySelfHosted() { Mockito.when(licenseHolder.get()).thenReturn(null); - Mockito.when(licenseHolder.getNoLicenseSeats()).thenReturn(5L); - Mockito.when(licenseHolder.getSeats()).thenReturn(3L); + Mockito.when(licenseHolder.getSeats()).thenReturn(5L); when().get("/billing") .then().statusCode(200) .body("hubId", is("42")) diff --git a/backend/src/test/java/org/cryptomator/hub/license/InitialLicenseIT.java b/backend/src/test/java/org/cryptomator/hub/license/InitialLicenseIT.java deleted file mode 100644 index 81b872f7a..000000000 --- a/backend/src/test/java/org/cryptomator/hub/license/InitialLicenseIT.java +++ /dev/null @@ -1,171 +0,0 @@ -package org.cryptomator.hub.license; - -import com.auth0.jwt.exceptions.JWTVerificationException; -import com.auth0.jwt.interfaces.DecodedJWT; -import io.quarkus.arc.Arc; -import io.quarkus.panache.mock.PanacheMock; -import io.quarkus.test.InjectMock; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; -import jakarta.inject.Inject; -import org.cryptomator.hub.entities.Settings; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import java.util.Map; - -@QuarkusTest -@TestProfile(InitialLicenseIT.ValidInitPropsInstanceTestProfile.class) -public class InitialLicenseIT { - - @Inject - LicenseHolder holder; - - @InjectMock - LicenseValidator validator; - - Settings settings = Mockito.spy(new Settings()); - - @InjectMock - RandomMinuteSleeper randomMinuteSleeper; - - @BeforeEach - public void setup( ) throws InterruptedException { - Mockito.doNothing().when(randomMinuteSleeper).sleep(); - PanacheMock.mock(Settings.class); // see https://quarkus.io/guides/hibernate-orm-panache#mocking - Mockito.when(Settings.get()).thenReturn(settings); - Mockito.doNothing().when(settings).persistAndFlush(); - - // recreate bean to start with a fresh instance: - // init() implicitly called due to @PostConstruct which messes with our invocation count - // See https://github.com/cryptomator/hub/pull/229#discussion_r1374694626 for further information - try (var instance = Arc.container().instance(LicenseHolder.class)) { - if (instance.isAvailable()) { - instance.destroy(); - Assertions.assertDoesNotThrow(holder::isSet); // recreate - } - } - Mockito.clearInvocations(validator); - Mockito.clearInvocations(settings); - } - - @Nested - @DisplayName("If no token is stored in DB") - public class WithoutTokenInDatabase { - - @BeforeEach - public void setup() { - settings.hubId = "42"; - settings.licenseKey = null; - } - - @Test - @DisplayName("persist hub.initial-license if it is valid") - public void testValidInitTokenSet() { - var decodedJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(validator.validate("initialToken", "42")).thenReturn(decodedJWT); - - holder.init(); - - Mockito.verify(validator).validate("initialToken", "42"); - Mockito.verify(settings).persistAndFlush(); - Assertions.assertEquals("initialToken", settings.licenseKey); - Assertions.assertEquals(decodedJWT, holder.get()); - } - - @Test - @DisplayName("no-op if hub.initial-license is invalid") - public void testInitTokenOnFailedValidationNotSet() { - Mockito.when(validator.validate("initialToken", "42")).thenThrow(new JWTVerificationException("")); - - holder.init(); - - Mockito.verify(validator).validate("initialToken", "42"); - Mockito.verify(settings, Mockito.never()).persistAndFlush(); - Assertions.assertNull(settings.licenseKey); - Assertions.assertNull(holder.get()); - } - - } - - @Nested - @DisplayName("If an existing token is stored in DB") - public class WithTokenInDatabase { - - @BeforeEach - public void setup() { - settings.hubId = "42"; - settings.licenseKey = "oldToken"; - } - - @Test - @DisplayName("valid hub.initial-license is ignored") - public void testValidDBTokenIgnoresValidInitToken() { - var decodedJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(validator.validate("oldToken", "42")).thenReturn(decodedJWT); - settings.hubId = "42"; - settings.licenseKey = "oldToken"; - - holder.init(); - - Mockito.verify(validator, Mockito.never()).validate("initialToken", "42"); - Mockito.verify(validator).validate("oldToken", "42"); - PanacheMock.verify(Settings.class, Mockito.never()).persistAndFlush(); - Assertions.assertEquals("oldToken", settings.licenseKey); - Assertions.assertEquals(decodedJWT, holder.get()); - } - - @Test - @DisplayName("invalid hub.initial-license is ignored") - public void testValidDBTokenIgnoresInvalidInitToken() { - var decodedJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(validator.validate("oldToken", "42")).thenReturn(decodedJWT); - settings.hubId = "42"; - settings.licenseKey = "oldToken"; - - holder.init(); - - Mockito.verify(validator, Mockito.never()).validate("initialToken", "42"); - Mockito.verify(validator).validate("oldToken", "42"); - Mockito.verify(settings, Mockito.never()).persistAndFlush(); - Assertions.assertEquals("oldToken", settings.licenseKey); - Assertions.assertEquals(decodedJWT, holder.get()); - } - - @Test - @DisplayName("explicitly setting a new token overwrites the existing one") - public void testSetValidToken() { - var decodedJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(validator.validate("newToken", "42")).thenReturn(decodedJWT); - settings.hubId = "42"; - settings.licenseKey = "oldToken"; - - holder.set("newToken"); - - Mockito.verify(validator).validate("newToken", "42"); - Mockito.verify(settings).persistAndFlush(); - Assertions.assertEquals("newToken", settings.licenseKey); - Assertions.assertEquals(decodedJWT, holder.get()); - } - - - } - - - public static class ValidInitPropsInstanceTestProfile implements QuarkusTestProfile { - @Override - public Map getConfigOverrides() { - return Map.of("hub.initial-id", "42", "hub.initial-license", "initialToken"); - } - - @Override - public boolean disableGlobalTestResources() { - return true; - } - } -} diff --git a/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderIT.java b/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderIT.java deleted file mode 100644 index f0cc8cc8a..000000000 --- a/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderIT.java +++ /dev/null @@ -1,390 +0,0 @@ -package org.cryptomator.hub.license; - -import com.auth0.jwt.exceptions.JWTVerificationException; -import com.auth0.jwt.interfaces.DecodedJWT; -import io.quarkus.arc.Arc; -import io.quarkus.test.InjectMock; -import io.quarkus.test.junit.QuarkusTest; -import jakarta.inject.Inject; -import org.cryptomator.hub.entities.Settings; -import org.hibernate.Session; -import org.hibernate.query.Query; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import org.mockito.MockedStatic; -import org.mockito.Mockito; - -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.Flow; - -@QuarkusTest -public class LicenseHolderIT { - - @Inject - LicenseHolder holder; - - @Nested - @DisplayName("Testing PostConstruct Method") - class TestPostConstruct { - - @InjectMock - Session session; - - @InjectMock - LicenseValidator validator; - - @InjectMock - RandomMinuteSleeper randomMinuteSleeper; - - MockedStatic settingsClass; - - @BeforeEach - public void setup() throws InterruptedException { - Query mockQuery = Mockito.mock(Query.class); - Mockito.doNothing().when(session).persist(Mockito.any()); - Mockito.when(session.createQuery(Mockito.anyString())).thenReturn(mockQuery); - Mockito.when(mockQuery.getSingleResult()).thenReturn(0l); - Mockito.doNothing().when(randomMinuteSleeper).sleep(); - - Arc.container().instance(LicenseHolder.class).destroy(); - - settingsClass = Mockito.mockStatic(Settings.class); - } - - @AfterEach - public void teardown() { - settingsClass.close(); - } - - @Test - @DisplayName("If database token is valid, set it in license holder") - public void testValidDBTokenSet() { - var decodedJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(validator.validate("token", "42")).thenReturn(decodedJWT); - Settings settingsMock = new Settings(); - settingsMock.licenseKey = "token"; - settingsMock.hubId = "42"; - settingsClass.when(Settings::get).thenReturn(settingsMock); - - holder.init(); - - // init implicitly called due to @PostConstruct which increases the times to verify by 1 - // See https://github.com/cryptomator/hub/pull/229#discussion_r1374694626 for further information - Mockito.verify(validator, Mockito.times(2)).validate("token", "42"); - Assertions.assertEquals(decodedJWT, holder.get()); - } - - @Test - @DisplayName("If database token is invalid, do net set it in license holder and nullify db entry") - public void testDBTokenOnFailedValidationNotSet() { - Mockito.when(validator.validate(Mockito.anyString(), Mockito.anyString())).thenAnswer(invocationOnMock -> { - throw new JWTVerificationException(""); - }); - Settings settingsMock = new Settings(); - settingsMock.licenseKey = "token"; - settingsMock.hubId = "42"; - settingsClass.when(Settings::get).thenReturn(settingsMock); - - holder.init(); - - Mockito.verify(validator, Mockito.times(1)).validate("token", "42"); - Mockito.verify(session, Mockito.times(1)).persist(Mockito.eq(settingsMock)); - Assertions.assertNull(holder.get()); - } - - @Test - @DisplayName("If database token is null, do net set it in license holder") - public void testNullDBTokenNotSet() { - Settings settingsEntity = Mockito.mock(Settings.class); - settingsClass.when(Settings::get).thenReturn(settingsEntity); - - holder.init(); - - Mockito.verify(validator, Mockito.never()).validate(Mockito.anyString(), Mockito.anyString()); - Assertions.assertNull(holder.get()); - } - } - - // -- set -- - - @Nested - @DisplayName("Testing set() method of LicenseHolder") - class TestSetter { - - @InjectMock - Session session; - - @InjectMock - LicenseValidator validator; - - @InjectMock - RandomMinuteSleeper randomMinuteSleeper; - - MockedStatic settingsClass; - - @BeforeEach - public void setup() throws InterruptedException { - Query mockQuery = Mockito.mock(Query.class); - Mockito.doNothing().when(session).persist(Mockito.any()); - Mockito.when(session.createQuery(Mockito.anyString())).thenReturn(mockQuery); - Mockito.when(mockQuery.getSingleResult()).thenReturn(0l); - Mockito.doNothing().when(randomMinuteSleeper).sleep(); - - Arc.container().instance(LicenseHolder.class).destroy(); - - settingsClass = Mockito.mockStatic(Settings.class); - } - - @AfterEach - public void teardown() { - settingsClass.close(); - } - - @Test - @DisplayName("Setting a valid token validates and persists it to db") - public void testSetValidToken() { - var decodedJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(validator.validate("token", "42")).thenReturn(decodedJWT); - Settings settingsMock = new Settings(); - settingsMock.hubId = "42"; - settingsClass.when(Settings::get).thenReturn(settingsMock); - - holder.set("token"); - - Mockito.verify(validator, Mockito.times(1)).validate("token", "42"); - Mockito.verify(session, Mockito.times(1)).persist(Mockito.eq(settingsMock)); - Assertions.assertEquals("token", settingsMock.licenseKey); - Assertions.assertEquals(decodedJWT, holder.get()); - } - - @Test - @DisplayName("Setting an invalid token fails with exception") - public void testSetInvalidToken() { - Mockito.when(validator.validate("token", "42")).thenAnswer(invocationOnMock -> { - throw new JWTVerificationException(""); - }); - Settings settingsMock = new Settings(); - settingsMock.hubId = "42"; - settingsClass.when(Settings::get).thenReturn(settingsMock); - - Assertions.assertThrows(JWTVerificationException.class, () -> holder.set("token")); - - Mockito.verify(validator, Mockito.times(1)).validate("token", "42"); - Mockito.verify(session, Mockito.never()).persist(Mockito.any()); - Assertions.assertNull(holder.get()); - } - } - - @Nested - @DisplayName("Testing refreshLicense() method of LicenseHolder") - class TestRefreshLicense { - - private final String refreshURL = "https://foo.bar.baz/"; - - private final HttpRequest refreshRequst = HttpRequest.newBuilder() // - .uri(URI.create(refreshURL)) // - .headers("Content-Type", "application/x-www-form-urlencoded") // - .POST(HttpRequest.BodyPublishers.ofString("token=token")) // - .build(); - - @InjectMock - Session session; - - @InjectMock - LicenseValidator validator; - - @InjectMock - RandomMinuteSleeper randomMinuteSleeper; - - MockedStatic settingsClass; - - @BeforeEach - public void setup() throws InterruptedException { - Query mockQuery = Mockito.mock(Query.class); - Mockito.doNothing().when(session).persist(Mockito.any()); - Mockito.when(session.createQuery(Mockito.anyString())).thenReturn(mockQuery); - Mockito.when(mockQuery.getSingleResult()).thenReturn(0l); - Mockito.doNothing().when(randomMinuteSleeper).sleep(); - - Arc.container().instance(LicenseHolder.class).destroy(); - - settingsClass = Mockito.mockStatic(Settings.class); - } - - @AfterEach - public void teardown() { - settingsClass.close(); - } - - @Test - @DisplayName("Refreshing a valid token validates and persists it to db") - public void testRefreshingExistingValidTokenInculdingRefreshURL() throws IOException, InterruptedException { - var existingJWT = Mockito.mock(DecodedJWT.class); - var receivedJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(existingJWT.getToken()).thenReturn("token&foo=bar"); - Mockito.when(validator.validate("token", "42")).thenReturn(receivedJWT); - Mockito.when(validator.validate("token&foo=bar", "42")).thenReturn(existingJWT); - Settings settingsMock = new Settings(); - settingsMock.hubId = "42"; - settingsMock.licenseKey = "token&foo=bar"; - settingsClass.when(Settings::get).thenReturn(settingsMock); - - var refreshTokenContainingSpecialChars = HttpRequest.newBuilder() // - .uri(URI.create(refreshURL)) // - .headers("Content-Type", "application/x-www-form-urlencoded") // - .POST(HttpRequest.BodyPublishers.ofString("token=token%26foo%3Dbar")) // - .build(); - - var httpClient = Mockito.mock(HttpClient.class); - var response = Mockito.mock(HttpResponse.class); - Mockito.doAnswer(invocation -> { - HttpRequest httpRequest = invocation.getArgument(0); - Assertions.assertEquals(refreshTokenContainingSpecialChars, httpRequest); - Assertions.assertEquals("token=token%26foo%3Dbar", getResponseFromRequest(httpRequest)); - return response; - }).when(httpClient).send(Mockito.any(), Mockito.eq(HttpResponse.BodyHandlers.ofString())); - Mockito.when(response.body()).thenReturn("token"); - Mockito.when(response.statusCode()).thenReturn(200); - - holder.refreshLicense(refreshURL, existingJWT.getToken(), httpClient); - - Mockito.verify(validator, Mockito.times(1)).validate("token", "42"); - Mockito.verify(session, Mockito.times(1)).persist(Mockito.eq(settingsMock)); - Assertions.assertEquals(receivedJWT, holder.get()); - } - - @ParameterizedTest(name = "Refreshing a valid token but receiving \"{0}\" with status code does \"{1}\" not persists it to db") - @CsvSource(value = {"invalidToken,200", "'',200", "validToken,500"}) - public void testInvalidTokenReceivedLeadsToNoOp(String receivedToken, int receivedCode) throws IOException, InterruptedException { - var existingJWT = Mockito.mock(DecodedJWT.class); - var receivedJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(existingJWT.getToken()).thenReturn("token"); - Mockito.when(validator.validate("token", "42")).thenReturn(existingJWT); - if (receivedToken.equals("validToken")) { - Mockito.when(validator.validate(receivedToken, "42")).thenReturn(receivedJWT); - } else { - Mockito.when(validator.validate(receivedToken, "42")).thenAnswer(invocationOnMock -> { - throw new JWTVerificationException(""); - }); - } - Settings settingsMock = new Settings(); - settingsMock.hubId = "42"; - settingsMock.licenseKey = "token"; - settingsClass.when(Settings::get).thenReturn(settingsMock); - - var httpClient = Mockito.mock(HttpClient.class); - var response = Mockito.mock(HttpResponse.class); - Mockito.doAnswer(invocation -> { - HttpRequest httpRequest = invocation.getArgument(0); - Assertions.assertEquals(refreshRequst, httpRequest); - Assertions.assertEquals("token=token", getResponseFromRequest(httpRequest)); - return response; - }).when(httpClient).send(Mockito.any(), Mockito.eq(HttpResponse.BodyHandlers.ofString())); - Mockito.when(response.body()).thenReturn(receivedToken); - Mockito.when(response.statusCode()).thenReturn(receivedCode); - - holder.refreshLicense(refreshURL, existingJWT.getToken(), httpClient); - - // init implicitly called due to @PostConstruct which increases the times to verify by 1 - // See https://github.com/cryptomator/hub/pull/229#discussion_r1374694626 for further information - Mockito.verify(validator, Mockito.times(1)).validate("token", "42"); - Mockito.verify(session, Mockito.never()).persist(Mockito.any()); - Assertions.assertEquals(existingJWT, holder.get()); - } - - @Test - @DisplayName("Refreshing a valid token but IOException thrown does not persists it to db") - public void testCommunicationProblemLeadsToNoOp() throws IOException, InterruptedException { - var existingJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(existingJWT.getToken()).thenReturn("token"); - Mockito.when(validator.validate("token", "42")).thenReturn(existingJWT); - Settings settingsMock = new Settings(); - settingsMock.hubId = "42"; - settingsMock.licenseKey = "token"; - settingsClass.when(Settings::get).thenReturn(settingsMock); - - var httpClient = Mockito.mock(HttpClient.class); - Mockito.doAnswer(invocation -> { - HttpRequest httpRequest = invocation.getArgument(0); - Assertions.assertEquals(refreshRequst, httpRequest); - throw new IOException("Problem during communication"); - }).when(httpClient).send(Mockito.any(), Mockito.eq(HttpResponse.BodyHandlers.ofString())); - - holder.refreshLicense(refreshURL, existingJWT.getToken(), httpClient); - - // init implicitly called due to @PostConstruct which increases the times to verify by 1 - // See https://github.com/cryptomator/hub/pull/229#discussion_r1374694626 for further information - Mockito.verify(validator, Mockito.times(1)).validate("token", "42"); - Mockito.verify(session, Mockito.never()).persist(Mockito.any()); - Assertions.assertEquals(existingJWT, holder.get()); - } - - @Test - @DisplayName("Refreshing a valid token without refresh URL does not execute refreshLicense") - public void testNoOpExistingValidTokenExculdingRefreshURL() throws InterruptedException { - var existingJWT = Mockito.mock(DecodedJWT.class); - Mockito.when(validator.validate("token", "42")).thenReturn(existingJWT); - Mockito.when(validator.refreshUrl(existingJWT.getToken())).thenReturn(Optional.empty()); - Settings settingsMock = new Settings(); - settingsMock.hubId = "42"; - settingsMock.licenseKey = "token"; - settingsClass.when(Settings::get).thenReturn(settingsMock); - - holder.refreshLicenseScheduler(); - - // init implicitly called due to @PostConstruct which increases the times to verify by 1 - // See https://github.com/cryptomator/hub/pull/229#discussion_r1374694626 for further information - Mockito.verify(validator, Mockito.times(1)).validate("token", "42"); - Mockito.verify(session, Mockito.never()).persist(Mockito.any()); - Assertions.assertEquals(existingJWT, holder.get()); - } - - private String getResponseFromRequest(HttpRequest httpRequest) { - return httpRequest.bodyPublisher().map(p -> { - var bodySubscriber = HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8); - var flowSubscriber = new StringSubscriber(bodySubscriber); - p.subscribe(flowSubscriber); - return bodySubscriber.getBody().toCompletableFuture().join(); - }).get(); - } - - private record StringSubscriber(HttpResponse.BodySubscriber wrapped) implements Flow.Subscriber { - - @Override - public void onSubscribe(Flow.Subscription subscription) { - wrapped.onSubscribe(subscription); - } - - @Override - public void onNext(ByteBuffer item) { - wrapped.onNext(List.of(item)); - } - - @Override - public void onError(Throwable throwable) { - wrapped.onError(throwable); - } - - @Override - public void onComplete() { - wrapped.onComplete(); - } - } - } - -} - diff --git a/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderTest.java b/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderTest.java new file mode 100644 index 000000000..6b5bd13bc --- /dev/null +++ b/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderTest.java @@ -0,0 +1,403 @@ +package org.cryptomator.hub.license; + +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; +import org.cryptomator.hub.entities.Settings; +import org.cryptomator.hub.entities.SettingsRepository; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpResponse; +import java.util.Optional; +import java.util.stream.Stream; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class LicenseHolderTest { + + SettingsRepository settingsRepo = mock(SettingsRepository.class); + RandomMinuteSleeper randomMinuteSleeper = mock(RandomMinuteSleeper.class); + LicenseValidator validator = mock(LicenseValidator.class); + + LicenseHolder licenseHolder; + + @BeforeEach + public void resetTestclass() { + licenseHolder = new LicenseHolder(); + licenseHolder.licenseValidator = validator; + licenseHolder.settingsRepo = settingsRepo; + licenseHolder.randomMinuteSleeper = randomMinuteSleeper; + } + + @Nested + @DisplayName("Testing Init Method") + class TestInit { + + @Test + @DisplayName("If db token and hubId is set, call validateExisting") + public void testTokenAndIdPresentInDatabase() { + //to show check, that db has higher precedence + licenseHolder.initialId = Optional.of("43"); + licenseHolder.initialLicenseToken = Optional.of("initToken"); + + Settings settings = mock(Settings.class); + when(settings.getLicenseKey()).thenReturn("token"); + when(settings.getHubId()).thenReturn("42"); + when(settingsRepo.get()).thenReturn(settings); + + var licenseHolderSpy = Mockito.spy(licenseHolder); + licenseHolderSpy.init(); + verify(licenseHolderSpy).validateOrResetExistingLicense(settings); + } + + @DisplayName("If dbToken or dbHubId is null, use set init config values") + @ParameterizedTest + @MethodSource("provideInitValuesCases") + public void testInitValues(String dbToken, String dbHubId) { + Settings settings = mock(Settings.class); + when(settings.getLicenseKey()).thenReturn(dbToken); + when(settings.getHubId()).thenReturn(dbHubId); + when(settingsRepo.get()).thenReturn(settings); + + licenseHolder.initialLicenseToken = Optional.of("token"); + licenseHolder.initialId = Optional.of("43"); + + var licenseHolderSpy = Mockito.spy(licenseHolder); + licenseHolderSpy.init(); + verify(licenseHolderSpy).validateAndApplyInitLicense(settings, "token", "43"); + } + + public static Stream provideInitValuesCases() { + return Stream.of( + Arguments.of("dbToken", null), + Arguments.of(null, null), + Arguments.of(null, "42") + ); + } + + @DisplayName("Do nothing, if db and init have a null value") + @ParameterizedTest + @MethodSource("provideDoNothingCases") + public void testDoNothingCases(String dbToken, String dbHubId, String initToken, String initId) { + Settings settings = mock(Settings.class); + when(settings.getLicenseKey()).thenReturn(dbToken); + when(settings.getHubId()).thenReturn(dbHubId); + when(settingsRepo.get()).thenReturn(settings); + + licenseHolder.initialLicenseToken = Optional.ofNullable(initToken); + licenseHolder.initialId = Optional.ofNullable(initId); + + var licenseHolderSpy = Mockito.spy(licenseHolder); + licenseHolderSpy.init(); + verify(licenseHolderSpy, never()).validateOrResetExistingLicense(settings); + verify(licenseHolderSpy, never()).validateAndApplyInitLicense(Mockito.eq(settings), any(), any()); + } + + public static Stream provideDoNothingCases() { + return Stream.of( + Arguments.of("dbToken", null, null, "43"), + Arguments.of(null, "42", null, "43"), + Arguments.of(null, "42", "initToken", null), + Arguments.of("dbToken", null, "initToken", null) + ); + } + } + + @Test + @DisplayName("Valid db token does not change settings") + public void testValidateExistingSuccess() { + Settings settings = mock(Settings.class); + when(settings.getLicenseKey()).thenReturn("token"); + when(settings.getHubId()).thenReturn("42"); + when(settingsRepo.get()).thenReturn(settings); + + when(validator.validate("token", "42")).thenReturn(Mockito.mock(DecodedJWT.class)); + + licenseHolder.validateOrResetExistingLicense(settings); + verify(settings, never()).setHubId(any()); + verify(settings, never()).setLicenseKey(any()); + } + + @Test + @DisplayName("Invalid db token is set to null and persisted") + public void testValidateExistingFailure() { + Settings settings = mock(Settings.class); + when(settings.getLicenseKey()).thenReturn("token"); + when(settings.getHubId()).thenReturn("42"); + when(settingsRepo.get()).thenReturn(settings); + + when(validator.validate("token", "42")).thenThrow(JWTVerificationException.class); + + licenseHolder.validateOrResetExistingLicense(settings); + verify(settings, never()).setHubId(any()); + verify(settings).setLicenseKey(Mockito.isNull()); + verify(settingsRepo).persistAndFlush(settings); + } + + @Test + @DisplayName("Valid init token is persisted with hubID to db") + public void testApplyInitSuccess() { + Settings settings = mock(Settings.class); + + when(validator.validate("token", "42")).thenReturn(Mockito.mock(DecodedJWT.class)); + + licenseHolder.validateAndApplyInitLicense(settings, "token", "42"); + verify(settings).setHubId("42"); + verify(settings).setLicenseKey("token"); + verify(settingsRepo).persistAndFlush(settings); + } + + @Test + @DisplayName("Invalid init token does not change settings") + public void testApplyInitFailure() { + Settings settings = mock(Settings.class); + when(settings.getLicenseKey()).thenReturn("token"); + when(settings.getHubId()).thenReturn("42"); + when(settingsRepo.get()).thenReturn(settings); + + when(validator.validate("token", "42")).thenThrow(JWTVerificationException.class); + + licenseHolder.validateAndApplyInitLicense(settings, "token", "42"); + verify(settings, never()).setHubId(any()); + verify(settings, never()).setLicenseKey(any()); + } + + @Nested + @DisplayName("Testing set() method") + class TestSetter { + + @BeforeEach + public void setup() throws InterruptedException { + Mockito.doNothing().when(randomMinuteSleeper).sleep(); + } + + @Test + @DisplayName("Setting a valid token validates and persists it to db") + public void testSetValidToken() { + var decodedJWT = mock(DecodedJWT.class); + when(validator.validate("token", "42")).thenReturn(decodedJWT); + + Settings settings = mock(Settings.class); + when(settings.getHubId()).thenReturn("42"); + when(settingsRepo.get()).thenReturn(settings); + + licenseHolder.set("token"); + + verify(validator).validate("token", "42"); + verify(settings).setLicenseKey("token"); + verify(settingsRepo).persistAndFlush(settings); + Assertions.assertEquals(decodedJWT, licenseHolder.get()); //TODO: not very unit test like + } + + @Test + @DisplayName("Setting an invalid token fails with exception") + public void testSetInvalidToken() { + when(validator.validate("token", "42")).thenAnswer(invocationOnMock -> { + throw new JWTVerificationException(""); + }); + Settings settings = mock(Settings.class); + when(settings.getHubId()).thenReturn("42"); + when(settingsRepo.get()).thenReturn(settings); + + Assertions.assertThrows(JWTVerificationException.class, () -> licenseHolder.set("token")); + + verify(validator).validate("token", "42"); + verify(settingsRepo, never()).persist((Settings) any()); + Assertions.assertNull(licenseHolder.get()); //TODO: not very unit test like + } + } + + @Nested + @DisplayName("Testing refreshLicense()") + class RefreshLicense { + + @Test + @DisplayName("If license does not have a refreshUrl, skip refresh") + public void testRefreshLicenseNoRefreshURL() throws InterruptedException, IOException { + var licenseHolderSpy = Mockito.spy(licenseHolder); + + var licenseJwt = mock(DecodedJWT.class); + when(licenseJwt.getClaim("refreshUrl")).thenReturn(null); + when(licenseHolderSpy.get()).thenReturn(licenseJwt); + + licenseHolderSpy.refreshLicense(); + + verify(licenseHolderSpy, never()).requestLicenseRefresh(any(), any()); + verify(licenseHolderSpy, never()).set(any()); + verify(settingsRepo, never()).get(); + verify(settingsRepo, never()).persistAndFlush(any()); + } + + + @Test + @DisplayName("If license does not have a valid refreshUrl, skip refresh") + public void testRefreshLicenseBadURL() throws InterruptedException, IOException { + var licenseHolderSpy = Mockito.spy(licenseHolder); + + var refreshClaim = mock(Claim.class); + when(refreshClaim.asString()).thenReturn("*:not:an::uri"); + var licenseJwt = mock(DecodedJWT.class); + when(licenseJwt.getClaim("refreshUrl")).thenReturn(refreshClaim); + when(licenseHolderSpy.get()).thenReturn(licenseJwt); + + licenseHolderSpy.refreshLicense(); + + verify(licenseHolderSpy, never()).requestLicenseRefresh(any(), any()); + verify(licenseHolderSpy, never()).set(any()); + verify(settingsRepo, never()).get(); + verify(settingsRepo, never()).persistAndFlush(any()); + } + + @DisplayName("If license request throws, do not set license") + @ParameterizedTest + @MethodSource("provideRefreshLicenseFailingRequestCases") + public void testRefreshLicenseFailingRequest(Throwable t) throws InterruptedException, IOException { + var licenseHolderSpy = Mockito.spy(licenseHolder); + + var refreshClaim = mock(Claim.class); + when(refreshClaim.asString()).thenReturn("http://localhost:3000"); + var licenseJwt = mock(DecodedJWT.class); + when(licenseJwt.getClaim("refreshUrl")).thenReturn(refreshClaim); + when(licenseJwt.getToken()).thenReturn("token"); + when(licenseHolderSpy.get()).thenReturn(licenseJwt); + Mockito.doThrow(t).when(licenseHolderSpy).requestLicenseRefresh(any(), eq("token")); + + licenseHolderSpy.refreshLicense(); + + verify(licenseHolderSpy).requestLicenseRefresh(any(), eq("token")); + verify(licenseHolderSpy, never()).set(any()); + verify(settingsRepo, never()).get(); + verify(settingsRepo, never()).persistAndFlush(any()); + } + + static Stream provideRefreshLicenseFailingRequestCases() { + return Stream.of(new IOException(), new LicenseHolder.LicenseRefreshFailedException(500, "Server Error")); + } + + @Test + @DisplayName("Successful refresh request, but failing validation") + public void testRefreshLicenseFailedValidation() throws InterruptedException, IOException { + var licenseHolderSpy = Mockito.spy(licenseHolder); + + var refreshClaim = mock(Claim.class); + when(refreshClaim.asString()).thenReturn("http://localhost:3000"); + var licenseJwt = mock(DecodedJWT.class); + when(licenseJwt.getClaim("refreshUrl")).thenReturn(refreshClaim); + when(licenseJwt.getToken()).thenReturn("token"); + when(licenseHolderSpy.get()).thenReturn(licenseJwt); + Mockito.doReturn("newToken").when(licenseHolderSpy).requestLicenseRefresh(any(), eq("token")); + Mockito.doThrow(JWTVerificationException.class).when(licenseHolderSpy).set("newToken"); + + licenseHolderSpy.refreshLicense(); + + verify(licenseHolderSpy).requestLicenseRefresh(any(), eq("token")); + verify(licenseHolderSpy).set("newToken"); + verify(settingsRepo, never()).get(); + verify(settingsRepo, never()).persistAndFlush(any()); + } + + @Test + @DisplayName("Successful refresh request, but failing validation") + public void testRefreshLicenseSuccess() throws InterruptedException, IOException { + var licenseHolderSpy = Mockito.spy(licenseHolder); + + var refreshClaim = mock(Claim.class); + when(refreshClaim.asString()).thenReturn("http://localhost:3000"); + var licenseJwt = mock(DecodedJWT.class); + when(licenseJwt.getClaim("refreshUrl")).thenReturn(refreshClaim); + when(licenseJwt.getToken()).thenReturn("token"); + when(licenseHolderSpy.get()).thenReturn(licenseJwt); + Mockito.doReturn("newToken").when(licenseHolderSpy).requestLicenseRefresh(any(), eq("token")); + Mockito.doThrow(JWTVerificationException.class).when(licenseHolderSpy).set("newToken"); + + licenseHolderSpy.refreshLicense(); + + verify(licenseHolderSpy).requestLicenseRefresh(any(), eq("token")); + verify(licenseHolderSpy).set("newToken"); + verify(settingsRepo, never()).get(); + verify(settingsRepo, never()).persistAndFlush(any()); + } + + } + + @Nested + @DisplayName("Testing requestLicenseRefresh()") + class RequestLicenseRefresh { + + @Test + public void testSucess() throws IOException, InterruptedException { + URI refreshUrl = URI.create("https://localhost:3000"); + try( var httpClientMock = Mockito.mockStatic(HttpClient.class)) { + var httpClient = mock(HttpClient.class); + var httpBuilder = mock(HttpClient.Builder.class); + when(httpBuilder.build()).thenReturn(httpClient); + when(httpBuilder.followRedirects(any())).thenReturn(httpBuilder); + httpClientMock.when(HttpClient::newBuilder).thenReturn(httpBuilder); + + var response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(200); + when(response.body()).thenReturn("newToken"); + when(httpClient.send(argThat(request -> request.uri().equals(refreshUrl)), any())).thenReturn(response); + + var result = licenseHolder.requestLicenseRefresh(refreshUrl, "token"); + Assertions.assertEquals("newToken", result); + } + } + + @Test + public void test500Response() throws IOException, InterruptedException { + URI refreshUrl = URI.create("https://localhost:3000"); + try( var httpClientMock = Mockito.mockStatic(HttpClient.class)) { + var httpClient = mock(HttpClient.class); + var httpBuilder = mock(HttpClient.Builder.class); + when(httpBuilder.build()).thenReturn(httpClient); + when(httpBuilder.followRedirects(any())).thenReturn(httpBuilder); + httpClientMock.when(HttpClient::newBuilder).thenReturn(httpBuilder); + + var response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(500); + when(response.body()).thenReturn("newToken"); + when(httpClient.send(argThat(request -> request.uri().equals(refreshUrl)), any())).thenReturn(response); + + Assertions.assertThrows(LicenseHolder.LicenseRefreshFailedException.class,() -> licenseHolder.requestLicenseRefresh(refreshUrl, "token")); + } + } + + @Test + public void testEmtyBody() throws IOException, InterruptedException { + URI refreshUrl = URI.create("https://localhost:3000"); + try( var httpClientMock = Mockito.mockStatic(HttpClient.class)) { + var httpClient = mock(HttpClient.class); + var httpBuilder = mock(HttpClient.Builder.class); + when(httpBuilder.build()).thenReturn(httpClient); + when(httpBuilder.followRedirects(any())).thenReturn(httpBuilder); + httpClientMock.when(HttpClient::newBuilder).thenReturn(httpBuilder); + + var response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(200); + when(response.body()).thenReturn(""); + when(httpClient.send(argThat(request -> request.uri().equals(refreshUrl)), any())).thenReturn(response); + + Assertions.assertThrows(LicenseHolder.LicenseRefreshFailedException.class,() -> licenseHolder.requestLicenseRefresh(refreshUrl, "token")); + } + } + } + +} From a9550e08395e10637df9d7bd44d4fc9b9eaa43b5 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 21 Mar 2024 16:26:10 +0100 Subject: [PATCH 36/54] clean up --- .../cryptomator/hub/api/VaultResource.java | 1 - .../cryptomator/hub/entities/AccessToken.java | 18 +++++++++++++++- .../entities/EffectiveGroupMembership.java | 12 ----------- .../hub/license/LicenseValidator.java | 6 ------ .../src/main/resources/application.properties | 1 - .../hub/license/LicenseValidatorTest.java | 21 ------------------- 6 files changed, 17 insertions(+), 42 deletions(-) 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 6cdf4e0df..1e8e193f6 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -36,7 +36,6 @@ import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; import org.cryptomator.hub.entities.Group; import org.cryptomator.hub.entities.GroupRepository; -import org.cryptomator.hub.entities.LegacyAccessToken; import org.cryptomator.hub.entities.LegacyAccessTokenRepository; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.UserRepository; 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 646fa99a1..5a996eb0f 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java @@ -18,7 +18,7 @@ @Entity @Table(name = "access_token") @NamedQuery(name = "AccessToken.deleteByUser", query = """ - DELETE + DELETE FROM AccessToken a WHERE a.id.userId = :userId """) @@ -110,6 +110,22 @@ public static class AccessId implements Serializable { String userId; UUID vaultId; + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public UUID getVaultId() { + return vaultId; + } + + public void setVaultId(UUID vaultId) { + this.vaultId = vaultId; + } + public AccessId(String userId, UUID vaultId) { this.userId = userId; this.vaultId = vaultId; 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 ae800eb91..5ea179a19 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java @@ -14,18 +14,6 @@ @Entity @Immutable @Table(name = "effective_group_membership") -@NamedQuery(name = "EffectiveGroupMembership.countEGUs", query = """ - SELECT count( DISTINCT u) - FROM User u - INNER JOIN EffectiveGroupMembership egm ON u.id = egm.id.memberId - WHERE egm.id.groupId = :groupId - """) -@NamedQuery(name = "EffectiveGroupMembership.getEGUs", query = """ - SELECT DISTINCT u - FROM User u - INNER JOIN EffectiveGroupMembership egm ON u.id = egm.id.memberId - WHERE egm.id.groupId = :groupId - """) public class EffectiveGroupMembership { @EmbeddedId 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 2cb600622..1813251c1 100644 --- a/backend/src/main/java/org/cryptomator/hub/license/LicenseValidator.java +++ b/backend/src/main/java/org/cryptomator/hub/license/LicenseValidator.java @@ -67,10 +67,4 @@ public DecodedJWT validate(String token, String expectedHubId) throws JWTVerific } return jwt; } - - public Optional refreshUrl(String token) throws JWTVerificationException { - var jwt = verifier.verify(token); - return Optional.ofNullable(jwt.getClaim("refreshUrl").asString()); - } - } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index c6078f01d..e2e825cd6 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -58,7 +58,6 @@ quarkus.datasource.db-kind=postgresql quarkus.datasource.jdbc.driver=org.postgresql.Driver quarkus.datasource.jdbc.transaction-requirement=off quarkus.datasource.jdbc.max-size=16 -quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQLDialect quarkus.hibernate-orm.database.globally-quoted-identifiers=true quarkus.flyway.migrate-at-start=true quarkus.flyway.locations=classpath:org/cryptomator/hub/flyway 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 f409fab8f..e0c63ac29 100644 --- a/backend/src/test/java/org/cryptomator/hub/license/LicenseValidatorTest.java +++ b/backend/src/test/java/org/cryptomator/hub/license/LicenseValidatorTest.java @@ -71,25 +71,4 @@ public void testValidateMalformedToken() { }); } - @Test - @DisplayName("validate token's refreshURL") - public void testGetTokensRefreshUrl() { - Assertions.assertEquals(Optional.of("http://localhost:8787/hub/subscription?hub_id=42"), validator.refreshUrl(VALID_TOKEN)); - } - - @Test - @DisplayName("validate expired token's refreshURL") - public void testGetExpiredTokensRefreshUrl() { - // this should not throw an exception and return a JWT with an expired date - Assertions.assertEquals(Optional.of("http://localhost:8787/hub/subscription?hub_id=42"), validator.refreshUrl(EXPIRED_TOKEN)); - } - - @Test - @DisplayName("validate expired token's refreshURL with invalid signature") - public void testInvalidSignatureTokensRefreshUrl() { - Assertions.assertThrows(SignatureVerificationException.class, () -> { - validator.refreshUrl(TOKEN_WITH_INVALID_SIGNATURE); - }); - } - } From e4e277cb74fb6688445a42aaff46820a6b765ad9 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 21 Mar 2024 17:14:36 +0100 Subject: [PATCH 37/54] migrate Vault entity to repository pattern --- .../cryptomator/hub/api/UsersResource.java | 9 +- .../cryptomator/hub/api/VaultResource.java | 75 +++++----- .../org/cryptomator/hub/entities/Vault.java | 138 ++++++++++++++---- .../hub/entities/VaultRepository.java | 26 ++++ .../hub/filters/VaultRoleFilter.java | 7 +- .../cryptomator/hub/api/VaultResourceIT.java | 52 ++++--- .../hub/filters/VaultRoleFilterTest.java | 26 ++-- 7 files changed, 234 insertions(+), 99 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/VaultRepository.java 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 2c7ac27f1..78eba7091 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java @@ -22,6 +22,7 @@ import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.UserRepository; import org.cryptomator.hub.entities.Vault; +import org.cryptomator.hub.entities.VaultRepository; import org.cryptomator.hub.entities.events.VaultAccessGrantedEventRepository; import org.eclipse.microprofile.jwt.JsonWebToken; import org.eclipse.microprofile.openapi.annotations.Operation; @@ -49,6 +50,8 @@ public class UsersResource { UserRepository userRepo; @Inject DeviceRepository deviceRepo; + @Inject + VaultRepository vaultRepo; @Inject JsonWebToken jwt; @@ -89,11 +92,11 @@ public Response putMe(@Nullable @Valid UserDto dto) { public Response updateMyAccessTokens(@NotNull Map tokens) { var user = userRepo.findById(jwt.getSubject()); for (var entry : tokens.entrySet()) { - var vault = Vault.findById(entry.getKey()); + var vault = vaultRepo.findById(entry.getKey()); if (vault == null) { continue; // skip } - var token = accessTokenRepo.findById(new AccessToken.AccessId(user.getId(), vault.id)); + var token = accessTokenRepo.findById(new AccessToken.AccessId(user.getId(), vault.getId())); if (token == null) { token = new AccessToken(); token.setVault(vault); @@ -101,7 +104,7 @@ public Response updateMyAccessTokens(@NotNull Map tokens) { } token.setVaultKey(entry.getValue()); accessTokenRepo.persist(token); - vaultAccessGrantedEventRepo.log(user.getId(), vault.id, user.getId()); + vaultAccessGrantedEventRepo.log(user.getId(), vault.getId(), user.getId()); } return Response.ok().build(); } 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 1e8e193f6..c08f57b7c 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -41,6 +41,7 @@ import org.cryptomator.hub.entities.UserRepository; import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.VaultAccess; +import org.cryptomator.hub.entities.VaultRepository; import org.cryptomator.hub.entities.events.VaultAccessGrantedEventRepository; import org.cryptomator.hub.entities.events.VaultCreatedEventRepository; import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; @@ -101,6 +102,8 @@ public class VaultResource { EffectiveVaultAccessRepository effectiveVaultAccessRepo; @Inject LegacyAccessTokenRepository legacyAccessTokenRepo; + @Inject + VaultRepository vaultRepo; @Inject JsonWebToken jwt; @@ -121,9 +124,9 @@ public List getAccessible(@Nullable @QueryParam("role") VaultAccess.Ro var currentUserId = jwt.getSubject(); final Stream resultStream; if (role == null) { - resultStream = Vault.findAccessibleByUser(currentUserId); + resultStream = vaultRepo.findAccessibleByUser(currentUserId); } else { - resultStream = Vault.findAccessibleByUser(currentUserId, role); + resultStream = vaultRepo.findAccessibleByUser(currentUserId, role); } return resultStream.map(VaultDto::fromEntity).toList(); } @@ -136,7 +139,7 @@ public List getAccessible(@Nullable @QueryParam("role") VaultAccess.Ro @Operation(summary = "list all vaults corresponding to the given ids", description = "list for each id in the list its corresponding vault. Ignores all id's where a vault does not exist, ") @APIResponse(responseCode = "200") public List getSomeVaults(@QueryParam("ids") List vaultIds) { - return Vault.findAllInList(vaultIds).map(VaultDto::fromEntity).toList(); + return vaultRepo.findAllInList(vaultIds).map(VaultDto::fromEntity).toList(); } @GET @@ -146,7 +149,7 @@ public List getSomeVaults(@QueryParam("ids") List vaultIds) { @Transactional @Operation(summary = "list all vaults", description = "list all vaults in the system") public List getAllVaults() { - return Vault.findAll().stream().map(VaultDto::fromEntity).toList(); + return vaultRepo.findAll().stream().map(VaultDto::fromEntity).toList(); } @GET @@ -181,7 +184,7 @@ public List getDirectMembers(@PathParam("vaultId") UUID vaultId) { @APIResponse(responseCode = "404", description = "user not found") @ActiveLicense public Response addUser(@PathParam("vaultId") UUID vaultId, @PathParam("userId") @ValidId String userId, @QueryParam("role") @DefaultValue("MEMBER") VaultAccess.Role role) { - var vault = Vault.findById(vaultId); // should always be found, since @VaultRole filter would have triggered + var vault = vaultRepo.findById(vaultId); // should always be found, since @VaultRole filter would have triggered var user = userRepo.findByIdOptional(userId).orElseThrow(NotFoundException::new); var usedSeats = effectiveVaultAccessRepo.countSeatOccupyingUsers(); //check if license seats are free @@ -211,7 +214,7 @@ public Response addUser(@PathParam("vaultId") UUID vaultId, @PathParam("userId") @APIResponse(responseCode = "404", description = "group not found") @ActiveLicense public Response addGroup(@PathParam("vaultId") UUID vaultId, @PathParam("groupId") @ValidId String groupId, @QueryParam("role") @DefaultValue("MEMBER") VaultAccess.Role role) { - var vault = Vault.findById(vaultId); // should always be found, since @VaultRole filter would have triggered + var vault = vaultRepo.findById(vaultId); // should always be found, since @VaultRole filter would have triggered var group = groupRepo.findByIdOptional(groupId).orElseThrow(NotFoundException::new); //usersInGroup - usersInGroupAndPartOfAtLeastOneVault + usersOfAtLeastOneVault @@ -223,13 +226,13 @@ public Response addGroup(@PathParam("vaultId") UUID vaultId, @PathParam("groupId } private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role role) { - var id = new VaultAccess.Id(vault.id, authority.getId()); + var id = new VaultAccess.Id(vault.getId(), authority.getId()); var existingAccess = VaultAccess.findByIdOptional(id); if (existingAccess.isPresent()) { var access = existingAccess.get(); access.role = role; access.persist(); - vaultMemberUpdatedEventRepo.log(jwt.getSubject(), vault.id, authority.getId(), role); + vaultMemberUpdatedEventRepo.log(jwt.getSubject(), vault.getId(), authority.getId(), role); return Response.ok().build(); } else { var access = new VaultAccess(); @@ -237,7 +240,7 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role access.authority = authority; access.role = role; access.persist(); - vaultMemberAddedEventRepo.log(jwt.getSubject(), vault.id, authority.getId(), role); + vaultMemberAddedEventRepo.log(jwt.getSubject(), vault.getId(), authority.getId(), role); return Response.created(URI.create(".")).build(); } } @@ -286,8 +289,8 @@ public List getUsersRequiringAccessGrant(@PathParam("vaultId") UUID vau @APIResponse(responseCode = "410", description = "Vault is archived") @ActiveLicense public Response legacyUnlock(@PathParam("vaultId") UUID vaultId, @PathParam("deviceId") @ValidId String deviceId) { - var vault = Vault.findByIdOptional(vaultId).orElseThrow(NotFoundException::new); - if (vault.archived) { + var vault = vaultRepo.findByIdOptional(vaultId).orElseThrow(NotFoundException::new); + if (vault.isArchived()) { throw new GoneException("Vault is archived."); } @@ -323,8 +326,8 @@ public Response legacyUnlock(@PathParam("vaultId") UUID vaultId, @PathParam("dev @APIResponse(responseCode = "449", description = "User account not yet initialized. Retry after setting up user") @ActiveLicense // may throw 402 public Response unlock(@PathParam("vaultId") UUID vaultId, @QueryParam("evenIfArchived") @DefaultValue("false") boolean ignoreArchived) { - var vault = Vault.findById(vaultId); // should always be found, since @VaultRole filter would have triggered - if (vault.archived && !ignoreArchived) { + var vault = vaultRepo.findById(vaultId); // should always be found, since @VaultRole filter would have triggered + if (vault.isArchived() && !ignoreArchived) { throw new GoneException("Vault is archived."); } @@ -344,7 +347,7 @@ public Response unlock(@PathParam("vaultId") UUID vaultId, @QueryParam("evenIfAr var subscriptionStateHeaderName = "Hub-Subscription-State"; var subscriptionStateHeaderValue = license.isSet() ? "ACTIVE" : "INACTIVE"; // license expiration is not checked here, because it is checked in the ActiveLicense filter return Response.ok(access.getVaultKey()).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); - } else if (Vault.findById(vaultId) == null) { + } else if (vaultRepo.findById(vaultId) == null) { throw new NotFoundException("No such vault."); } else { vaultKeyRetrievedEventRepo.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); @@ -365,8 +368,8 @@ public Response unlock(@PathParam("vaultId") UUID vaultId, @QueryParam("evenIfAr @APIResponse(responseCode = "404", description = "at least one user has not been found") @APIResponse(responseCode = "410", description = "vault is archived") public Response grantAccess(@PathParam("vaultId") UUID vaultId, @NotEmpty Map tokens) { - var vault = Vault.findById(vaultId); // should always be found, since @VaultRole filter would have triggered - if (vault.archived) { + var vault = vaultRepo.findById(vaultId); // should always be found, since @VaultRole filter would have triggered + if (vault.isArchived()) { throw new GoneException("Vault is archived."); } @@ -403,8 +406,8 @@ public Response grantAccess(@PathParam("vaultId") UUID vaultId, @NotEmpty MapfindByIdOptional(vaultId).orElseThrow(NotFoundException::new); - if (vault.effectiveMembers.stream().noneMatch(u -> u.getId().equals(jwt.getSubject())) && !identity.getRoles().contains("admin")) { + Vault vault = vaultRepo.findByIdOptional(vaultId).orElseThrow(NotFoundException::new); + if (vault.getEffectiveMembers().stream().noneMatch(u -> u.getId().equals(jwt.getSubject())) && !identity.getRoles().contains("admin")) { throw new ForbiddenException("Requesting user is not a member of the vault"); } return VaultDto.fromEntity(vault); @@ -424,7 +427,7 @@ public VaultDto get(@PathParam("vaultId") UUID vaultId) { @APIResponse(responseCode = "402", description = "number of licensed seats is exceeded") public Response createOrUpdate(@PathParam("vaultId") UUID vaultId, @Valid @NotNull VaultDto vaultDto) { User currentUser = userRepo.findById(jwt.getSubject()); - Optional existingVault = Vault.findByIdOptional(vaultId); + Optional existingVault = vaultRepo.findByIdOptional(vaultId); final Vault vault; if (existingVault.isPresent()) { // load existing vault: @@ -437,17 +440,17 @@ public Response createOrUpdate(@PathParam("vaultId") UUID vaultId, @Valid @NotNu } // create new vault: vault = new Vault(); - vault.id = vaultDto.id; - vault.creationTime = Instant.now().truncatedTo(ChronoUnit.MILLIS); + vault.setId(vaultDto.id); + vault.setCreationTime(Instant.now().truncatedTo(ChronoUnit.MILLIS)); } // set regardless of whether vault is new or existing: - vault.name = vaultDto.name; - vault.description = vaultDto.description; - vault.archived = existingVault.isEmpty() ? false : vaultDto.archived; + vault.setName(vaultDto.name); + vault.setDescription(vaultDto.description); + vault.setArchived(existingVault.isEmpty() ? false : vaultDto.archived); - vault.persistAndFlush(); // trigger PersistenceException before we continue with + vaultRepo.persistAndFlush(vault); // trigger PersistenceException before we continue with if (existingVault.isEmpty()) { - vaultCreatedEventRepo.log(currentUser.getId(), vault.id, vault.name, vault.description); + vaultCreatedEventRepo.log(currentUser.getId(), vault.getId(), vault.getName(), vault.getDescription()); var access = new VaultAccess(); access.vault = vault; @@ -457,7 +460,7 @@ public Response createOrUpdate(@PathParam("vaultId") UUID vaultId, @Valid @NotNu vaultMemberAddedEventRepo.log(currentUser.getId(), vaultId, currentUser.getId(), VaultAccess.Role.OWNER); return Response.created(URI.create(".")).contentLocation(URI.create(".")).entity(VaultDto.fromEntity(vault)).type(MediaType.APPLICATION_JSON).build(); } else { - vaultUpdatedEventRepo.log(currentUser.getId(), vault.id, vault.name, vault.description, vault.archived); + vaultUpdatedEventRepo.log(currentUser.getId(), vault.getId(), vault.getName(), vault.getDescription(), vault.isArchived()); return Response.ok(VaultDto.fromEntity(vault), MediaType.APPLICATION_JSON).build(); } } @@ -475,10 +478,10 @@ public Response createOrUpdate(@PathParam("vaultId") UUID vaultId, @Valid @NotNu @APIResponse(responseCode = "409", description = "owned by another user") public Response claimOwnership(@PathParam("vaultId") UUID vaultId, @FormParam("proof") @Valid @ValidJWS String proof) { User currentUser = userRepo.findById(jwt.getSubject()); - Vault vault = Vault.findByIdOptional(vaultId).orElseThrow(NotFoundException::new); + Vault vault = vaultRepo.findByIdOptional(vaultId).orElseThrow(NotFoundException::new); // if vault.authenticationPublicKey no longer exists, this vault has already been claimed by a different user - var authPubKey = vault.getAuthenticationPublicKey().orElseThrow(() -> new ClientErrorException(Response.Status.CONFLICT)); + var authPubKey = vault.getAuthenticationPublicKeyOptional().orElseThrow(() -> new ClientErrorException(Response.Status.CONFLICT)); try { var verifier = JWT.require(Algorithm.ECDSA384(authPubKey)) @@ -508,12 +511,12 @@ public Response claimOwnership(@PathParam("vaultId") UUID vaultId, @FormParam("p vaultMemberAddedEventRepo.log(currentUser.getId(), vaultId, currentUser.getId(), VaultAccess.Role.OWNER); } - vault.salt = null; - vault.iterations = null; - vault.masterkey = null; - vault.authenticationPrivateKey = null; - vault.authenticationPublicKey = null; - vault.persist(); + vault.setSalt(null); + vault.setIterations(null); + vault.setMasterkey(null); + vault.setAuthenticationPrivateKey(null); + vault.setAuthenticationPublicKey(null); + vaultRepo.persist(vault); vaultOwnershipClaimedEventRepo.log(currentUser.getId(), vaultId); return Response.ok(VaultDto.fromEntity(vault), MediaType.APPLICATION_JSON).build(); @@ -532,7 +535,7 @@ public record VaultDto(@JsonProperty("id") UUID id, ) { public static VaultDto fromEntity(Vault entity) { - return new VaultDto(entity.id, entity.name, entity.description, entity.archived, entity.creationTime.truncatedTo(ChronoUnit.MILLIS), entity.masterkey, entity.iterations, entity.salt, entity.authenticationPublicKey, entity.authenticationPrivateKey); + return new VaultDto(entity.getId(), entity.getName(), entity.getDescription(), entity.isArchived(), entity.getCreationTime().truncatedTo(ChronoUnit.MILLIS), entity.getMasterkey(), entity.getIterations(), entity.getSalt(), entity.getAuthenticationPublicKey(), entity.getAuthenticationPrivateKey()); } } 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 682b17e10..cad96b99f 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,5 @@ package org.cryptomator.hub.entities; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.panache.common.Parameters; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -22,13 +20,11 @@ import java.time.Instant; import java.util.Base64; import java.util.HashSet; -import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; -import java.util.stream.Stream; @Entity @Table(name = "vault") @@ -52,11 +48,11 @@ WHERE v.id IN :ids """ ) -public class Vault extends PanacheEntityBase { +public class Vault { @Id @Column(name = "id", nullable = false) - public UUID id; + UUID id; @ManyToMany @Immutable @@ -64,7 +60,7 @@ public class Vault extends PanacheEntityBase { joinColumns = @JoinColumn(name = "vault_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id") ) - public Set directMembers = new HashSet<>(); + Set directMembers = new HashSet<>(); @ManyToMany @Immutable @@ -72,39 +68,39 @@ public class Vault extends PanacheEntityBase { joinColumns = @JoinColumn(name = "vault_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id") ) - public Set effectiveMembers = new HashSet<>(); + Set effectiveMembers = new HashSet<>(); @OneToMany(mappedBy = "vault", fetch = FetchType.LAZY) - public Set accessTokens = new HashSet<>(); + Set accessTokens = new HashSet<>(); @Column(name = "name", nullable = false) - public String name; + String name; @Column(name = "salt") - public String salt; + String salt; @Column(name = "iterations") - public Integer iterations; + Integer iterations; @Column(name = "masterkey") - public String masterkey; + String masterkey; @Column(name = "auth_pubkey") - public String authenticationPublicKey; + String authenticationPublicKey; @Column(name = "auth_prvkey") - public String authenticationPrivateKey; + String authenticationPrivateKey; @Column(name = "creation_time", nullable = false) - public Instant creationTime; + Instant creationTime; @Column(name = "description") - public String description; + String description; @Column(name = "archived", nullable = false) - public boolean archived; + boolean archived; - public Optional getAuthenticationPublicKey() { + public Optional getAuthenticationPublicKeyOptional() { if (authenticationPublicKey == null) { return Optional.empty(); } @@ -125,16 +121,108 @@ public Optional getAuthenticationPublicKey() { } } - public static Stream findAccessibleByUser(String userId) { - return find("#Vault.accessibleByUser", Parameters.with("userId", userId)).stream(); + public UUID getId() { + return id; } - public static Stream findAccessibleByUser(String userId, VaultAccess.Role role) { - return find("#Vault.accessibleByUserAndRole", Parameters.with("userId", userId).and("role", role)).stream(); + public void setId(UUID id) { + this.id = id; } - public static Stream findAllInList(List ids) { - return find("#Vault.allInList", Parameters.with("ids", ids)).stream(); + public Set getDirectMembers() { + return directMembers; + } + + public void setDirectMembers(Set directMembers) { + this.directMembers = directMembers; + } + + public Set getEffectiveMembers() { + return effectiveMembers; + } + + public void setEffectiveMembers(Set effectiveMembers) { + this.effectiveMembers = effectiveMembers; + } + + public Set getAccessTokens() { + return accessTokens; + } + + public void setAccessTokens(Set accessTokens) { + this.accessTokens = accessTokens; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSalt() { + return salt; + } + + public void setSalt(String salt) { + this.salt = salt; + } + + public Integer getIterations() { + return iterations; + } + + public void setIterations(Integer iterations) { + this.iterations = iterations; + } + + public String getMasterkey() { + return masterkey; + } + + public void setMasterkey(String masterkey) { + this.masterkey = masterkey; + } + + public void setAuthenticationPublicKey(String authenticationPublicKey) { + this.authenticationPublicKey = authenticationPublicKey; + } + + public String getAuthenticationPrivateKey() { + return authenticationPrivateKey; + } + + public String getAuthenticationPublicKey() { + return authenticationPublicKey; + } + + public void setAuthenticationPrivateKey(String authenticationPrivateKey) { + this.authenticationPrivateKey = authenticationPrivateKey; + } + + public Instant getCreationTime() { + return creationTime; + } + + public void setCreationTime(Instant creationTime) { + this.creationTime = creationTime; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public boolean isArchived() { + return archived; + } + + public void setArchived(boolean archived) { + this.archived = archived; } @Override diff --git a/backend/src/main/java/org/cryptomator/hub/entities/VaultRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/VaultRepository.java new file mode 100644 index 000000000..47d168a65 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/VaultRepository.java @@ -0,0 +1,26 @@ +package org.cryptomator.hub.entities; + +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import io.quarkus.panache.common.Parameters; +import jakarta.enterprise.context.ApplicationScoped; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; + +@ApplicationScoped +public class VaultRepository implements PanacheRepositoryBase { + + public Stream findAccessibleByUser(String userId) { + return find("#Vault.accessibleByUser", Parameters.with("userId", userId)).stream(); + } + + public Stream findAccessibleByUser(String userId, VaultAccess.Role role) { + return find("#Vault.accessibleByUserAndRole", Parameters.with("userId", userId).and("role", role)).stream(); + } + + public Stream findAllInList(List ids) { + return find("#Vault.allInList", Parameters.with("ids", ids)).stream(); + } + +} 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 6e3084988..b70916e65 100644 --- a/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java +++ b/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java @@ -9,10 +9,9 @@ import jakarta.ws.rs.container.ResourceInfo; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.ext.Provider; -import org.cryptomator.hub.entities.EffectiveVaultAccess; import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; -import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.VaultAccess; +import org.cryptomator.hub.entities.VaultRepository; import org.eclipse.microprofile.jwt.JsonWebToken; import java.util.Arrays; @@ -33,6 +32,8 @@ public class VaultRoleFilter implements ContainerRequestFilter { @Inject EffectiveVaultAccessRepository effectiveVaultAccessRepo; + @Inject + VaultRepository vaultRepo; @Context ResourceInfo resourceInfo; @@ -54,7 +55,7 @@ public void filter(ContainerRequestContext requestContext) throws NotFoundExcept } var forbiddenMsg = "Vault role required: " + Arrays.stream(annotation.value()).map(VaultAccess.Role::name).collect(Collectors.joining(", ")); - if (Vault.findByIdOptional(vaultId).isPresent()) { + if (vaultRepo.findByIdOptional(vaultId).isPresent()) { // check permissions for existing vault: var effectiveRoles = effectiveVaultAccessRepo.listRoles(vaultId, userId); if (Arrays.stream(annotation.value()).noneMatch(effectiveRoles::contains)) { diff --git a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java index c85bea156..bd54b8e39 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java +++ b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java @@ -10,14 +10,15 @@ import io.restassured.RestAssured; import io.restassured.http.ContentType; import jakarta.inject.Inject; -import jakarta.transaction.Transactional; import jakarta.validation.Validator; import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; -import org.cryptomator.hub.entities.Vault; +import org.cryptomator.hub.rollback.DBRollback; +import org.flywaydb.core.Flyway; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Nested; @@ -61,9 +62,10 @@ public class VaultResourceIT { AgroalDataSource dataSource; @Inject EffectiveVaultAccessRepository effectiveVaultAccessRepo; - @Inject Validator validator; + @Inject + public Flyway flyway; @BeforeAll public static void beforeAll() { @@ -227,6 +229,12 @@ public void testUnlockArchived() { }) public class AsAuthorizedUser2 { + @BeforeEach + public void resetDB() { + flyway.clean(); + flyway.migrate(); + } + @Test @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/access-token returns 449, because user2 is not initialized") public void testUnlock() { @@ -288,6 +296,7 @@ public void testCreateVault3() { @Test @Order(2) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100003333 returns 200, updating only name, description and archive flag") + @DBRollback public void testUpdateVault() { var uuid = UUID.fromString("7E57C0DE-0000-4000-8000-000100003333"); var vaultDto = new VaultResource.VaultDto(uuid, "VaultUpdated", "Vault updated.", true, Instant.parse("2222-11-11T11:11:11Z"), "doNotUpdate", 27, "doNotUpdate", "doNotUpdate", "doNotUpdate"); @@ -433,12 +442,12 @@ public void getMembersOfVault1() { @Test @Order(5) - @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/users-requiring-access-grant does contains user2 via group membership") + @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/users-requiring-access-grant contains user2 via group membership") public void testGetUsersRequiringAccess1() throws SQLException { try (var c = dataSource.getConnection(); var s = c.createStatement()) { s.execute(""" - UPDATE - "user_details" SET publickey='public2', privatekey='private2', setupcode='setup2' + UPDATE "user_details" + SET publickey='public2', privatekey='private2', setupcode='setup2' WHERE id='user2'; """); } @@ -527,6 +536,7 @@ public void testRevokeAccess() { // previously added in testGrantAccess() @Test @Order(14) @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/members does not contain user2") + @DBRollback public void getMembersOfVault2c() { given().when().get("/vaults/{vaultId}/members", "7E57C0DE-0000-4000-8000-000100002222") .then().statusCode(200) @@ -627,21 +637,13 @@ public void removeGroup2() { @Test @Order(8) @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/members does not contain group2") + @DBRollback public void getMembersOfVault1b() { given().when().get("/vaults/{vaultId}/members", "7E57C0DE-0000-4000-8000-000100001111") .then().statusCode(200) .body("id", not(hasItems("group2"))); } - @AfterAll - public void cleanup() throws SQLException { - try (var c = dataSource.getConnection(); var s = c.createStatement()) { - s.execute(""" - DELETE FROM "authority" WHERE ID = 'user999'; - """); - } - } - } @Nested @@ -842,19 +844,23 @@ public class ClaimOwnership { private static Algorithm JWT_ALG; @BeforeAll - @Transactional - public static void setup() throws GeneralSecurityException { + public void setup() throws GeneralSecurityException, SQLException { var keyPairGen = KeyPairGenerator.getInstance("EC"); keyPairGen.initialize(new ECGenParameterSpec("secp384r1")); var keyPair = keyPairGen.generateKeyPair(); JWT_ALG = Algorithm.ECDSA384((ECPrivateKey) keyPair.getPrivate()); + var authPubKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()); - Vault v = new Vault(); - v.id = UUID.fromString("7E57C0DE-0000-4000-8000-000100009999"); - v.name = "ownership-test-vault"; - v.creationTime = Instant.now(); - v.authenticationPublicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()); - v.persist(); + try (var c = dataSource.getConnection(); var s = c.createStatement()) { + s.execute(""" + INSERT INTO "vault" ("id", "name", "description", "creation_time", "salt", "iterations", "masterkey", "auth_pubkey", "auth_prvkey", "archived") + VALUES + ('7E57C0DE-0000-4000-8000-000100009999', 'ownership-test-vault', 'Testing ownership.', '2020-02-20 20:20:20', NULL, NULL , NULL, + '%s', + NULL, + FALSE); + """.formatted(authPubKey)); + } } @Test 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 087e77fa3..02bec32a2 100644 --- a/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java +++ b/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java @@ -1,30 +1,29 @@ package org.cryptomator.hub.filters; -import io.quarkus.test.junit.QuarkusTest; -import jakarta.annotation.Nullable; import jakarta.ws.rs.ForbiddenException; import jakarta.ws.rs.NotAuthorizedException; import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ResourceInfo; import jakarta.ws.rs.core.MultivaluedHashMap; -import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.UriInfo; import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; +import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.VaultAccess; +import org.cryptomator.hub.entities.VaultRepository; import org.eclipse.microprofile.jwt.JsonWebToken; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; import org.mockito.Mockito; -import java.lang.reflect.Method; import java.util.Map; +import java.util.Optional; import java.util.Set; -@QuarkusTest public class VaultRoleFilterTest { private final ResourceInfo resourceInfo = Mockito.mock(ResourceInfo.class); @@ -32,6 +31,7 @@ public class VaultRoleFilterTest { private final ContainerRequestContext context = Mockito.mock(ContainerRequestContext.class); private final JsonWebToken jwt = Mockito.mock(JsonWebToken.class); private final EffectiveVaultAccessRepository effectiveVaultAccessRepo = Mockito.mock(EffectiveVaultAccessRepository.class); + private final VaultRepository vaultRepo = Mockito.mock(VaultRepository.class); private final VaultRoleFilter filter = new VaultRoleFilter(); @BeforeEach @@ -39,6 +39,7 @@ public void setup() { filter.resourceInfo = resourceInfo; filter.jwt = jwt; filter.effectiveVaultAccessRepo = effectiveVaultAccessRepo; + filter.vaultRepo = vaultRepo; Mockito.doReturn(uriInfo).when(context).getUriInfo(); } @@ -67,7 +68,9 @@ public void testFilterWithInsufficientPrivileges() throws NoSuchMethodException Mockito.doReturn(VaultRoleFilterTest.class.getMethod("allowOwner")).when(resourceInfo).getResourceMethod(); Mockito.doReturn(new MultivaluedHashMap<>(Map.of(VaultRole.DEFAULT_VAULT_ID_PARAM, "7E57C0DE-0000-4000-8000-000100001111"))).when(uriInfo).getPathParameters(); Mockito.doReturn("user2").when(jwt).getSubject(); - Mockito.when(effectiveVaultAccessRepo.listRoles(Mockito.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-000100001111")),Mockito.eq("user2"))).thenReturn(Set.of()); + + Mockito.when(vaultRepo.findByIdOptional(ArgumentMatchers.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-000100001111")))).thenReturn(Optional.of(Mockito.mock(Vault.class))); + Mockito.when(effectiveVaultAccessRepo.listRoles(Mockito.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-000100001111")), Mockito.eq("user2"))).thenReturn(Set.of()); var e = Assertions.assertThrows(ForbiddenException.class, () -> filter.filter(context)); @@ -80,7 +83,9 @@ public void testFilterSuccess1() throws NoSuchMethodException { Mockito.doReturn(VaultRoleFilterTest.class.getMethod("allowOwner")).when(resourceInfo).getResourceMethod(); Mockito.doReturn(new MultivaluedHashMap<>(Map.of(VaultRole.DEFAULT_VAULT_ID_PARAM, "7E57C0DE-0000-4000-8000-000100001111"))).when(uriInfo).getPathParameters(); Mockito.doReturn("user1").when(jwt).getSubject(); - Mockito.when(effectiveVaultAccessRepo.listRoles(Mockito.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-000100001111")),Mockito.eq("user1"))).thenReturn(Set.of(VaultAccess.Role.OWNER)); + + Mockito.when(vaultRepo.findByIdOptional(ArgumentMatchers.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-000100001111")))).thenReturn(Optional.of(Mockito.mock(Vault.class))); + Mockito.when(effectiveVaultAccessRepo.listRoles(Mockito.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-000100001111")), Mockito.eq("user1"))).thenReturn(Set.of(VaultAccess.Role.OWNER)); Assertions.assertDoesNotThrow(() -> filter.filter(context)); } @@ -91,7 +96,9 @@ public void testFilterSuccess2() throws NoSuchMethodException { Mockito.doReturn(VaultRoleFilterTest.class.getMethod("allowOwner")).when(resourceInfo).getResourceMethod(); Mockito.doReturn(new MultivaluedHashMap<>(Map.of(VaultRole.DEFAULT_VAULT_ID_PARAM, "7E57C0DE-0000-4000-8000-000100002222"))).when(uriInfo).getPathParameters(); Mockito.doReturn("user2").when(jwt).getSubject(); - Mockito.when(effectiveVaultAccessRepo.listRoles(Mockito.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-000100002222")),Mockito.eq("user2"))).thenReturn(Set.of(VaultAccess.Role.OWNER)); + + Mockito.when(vaultRepo.findByIdOptional(ArgumentMatchers.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-000100002222")))).thenReturn(Optional.of(Mockito.mock(Vault.class))); + Mockito.when(effectiveVaultAccessRepo.listRoles(Mockito.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-000100002222")), Mockito.eq("user2"))).thenReturn(Set.of(VaultAccess.Role.OWNER)); Assertions.assertDoesNotThrow(() -> filter.filter(context)); } @@ -110,7 +117,8 @@ public void setup() { public void testFilterSuccess() throws NoSuchMethodException { Mockito.doReturn(VaultRoleFilterTest.class.getMethod("allowOwner")).when(resourceInfo).getResourceMethod(); Mockito.doReturn("user1").when(jwt).getSubject(); - Mockito.when(effectiveVaultAccessRepo.listRoles(Mockito.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-00010000AAAA")),Mockito.eq("user1"))).thenReturn(Set.of(VaultAccess.Role.OWNER)); + Mockito.when(vaultRepo.findByIdOptional(ArgumentMatchers.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-00010000AAAA")))).thenReturn(Optional.of(Mockito.mock(Vault.class))); + Mockito.when(effectiveVaultAccessRepo.listRoles(Mockito.argThat(uuid -> uuid.toString().equalsIgnoreCase("7E57C0DE-0000-4000-8000-00010000AAAA")), Mockito.eq("user1"))).thenReturn(Set.of(VaultAccess.Role.OWNER)); Assertions.assertDoesNotThrow(() -> filter.filter(context)); } From aae523973f3666fe202d4ea892b92dd118341e28 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 21 Mar 2024 17:37:25 +0100 Subject: [PATCH 38/54] migrate VaultAccess to panache repository pattern --- .../cryptomator/hub/api/VaultResource.java | 47 +++++++------- .../cryptomator/hub/entities/VaultAccess.java | 65 +++++++++++++++---- .../hub/entities/VaultAccessRepository.java | 17 +++++ 3 files changed, 95 insertions(+), 34 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/VaultAccessRepository.java 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 c08f57b7c..001e26e9b 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -41,6 +41,7 @@ import org.cryptomator.hub.entities.UserRepository; import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.VaultAccess; +import org.cryptomator.hub.entities.VaultAccessRepository; import org.cryptomator.hub.entities.VaultRepository; import org.cryptomator.hub.entities.events.VaultAccessGrantedEventRepository; import org.cryptomator.hub.entities.events.VaultCreatedEventRepository; @@ -104,6 +105,8 @@ public class VaultResource { LegacyAccessTokenRepository legacyAccessTokenRepo; @Inject VaultRepository vaultRepo; + @Inject + VaultAccessRepository vaultAccessRepo; @Inject JsonWebToken jwt; @@ -162,9 +165,9 @@ public List getAllVaults() { @APIResponse(responseCode = "200") @APIResponse(responseCode = "403", description = "not a vault owner") public List getDirectMembers(@PathParam("vaultId") UUID vaultId) { - return VaultAccess.forVault(vaultId).map(access -> switch (access.authority) { - case User u -> MemberDto.fromEntity(u, access.role); - case Group g -> MemberDto.fromEntity(g, access.role); + return vaultAccessRepo.forVault(vaultId).map(access -> switch (access.getAuthority()) { + case User u -> MemberDto.fromEntity(u, access.getRole()); + case Group g -> MemberDto.fromEntity(g, access.getRole()); default -> throw new IllegalStateException(); }).toList(); } @@ -227,19 +230,19 @@ public Response addGroup(@PathParam("vaultId") UUID vaultId, @PathParam("groupId private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role role) { var id = new VaultAccess.Id(vault.getId(), authority.getId()); - var existingAccess = VaultAccess.findByIdOptional(id); + var existingAccess = vaultAccessRepo.findByIdOptional(id); if (existingAccess.isPresent()) { var access = existingAccess.get(); - access.role = role; - access.persist(); + access.setRole(role); + vaultAccessRepo.persist(access); vaultMemberUpdatedEventRepo.log(jwt.getSubject(), vault.getId(), authority.getId(), role); return Response.ok().build(); } else { var access = new VaultAccess(); - access.vault = vault; - access.authority = authority; - access.role = role; - access.persist(); + access.setVault(vault); + access.setAuthority(authority); + access.setRole(role); + vaultAccessRepo.persist(access); vaultMemberAddedEventRepo.log(jwt.getSubject(), vault.getId(), authority.getId(), role); return Response.created(URI.create(".")).build(); } @@ -255,7 +258,7 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role @APIResponse(responseCode = "204", description = "authority removed") @APIResponse(responseCode = "403", description = "not a vault owner") public Response removeAuthority(@PathParam("vaultId") UUID vaultId, @PathParam("authorityId") @ValidId String authorityId) { - if (VaultAccess.deleteById(new VaultAccess.Id(vaultId, authorityId))) { + if (vaultAccessRepo.deleteById(new VaultAccess.Id(vaultId, authorityId))) { vaultMemberRemovedEventRepo.log(jwt.getSubject(), vaultId, authorityId); return Response.status(Response.Status.NO_CONTENT).build(); } else { @@ -453,10 +456,10 @@ public Response createOrUpdate(@PathParam("vaultId") UUID vaultId, @Valid @NotNu vaultCreatedEventRepo.log(currentUser.getId(), vault.getId(), vault.getName(), vault.getDescription()); var access = new VaultAccess(); - access.vault = vault; - access.authority = currentUser; - access.role = VaultAccess.Role.OWNER; - access.persist(); + access.setVault(vault); + access.setAuthority(currentUser); + access.setRole(VaultAccess.Role.OWNER); + vaultAccessRepo.persist(access); vaultMemberAddedEventRepo.log(currentUser.getId(), vaultId, currentUser.getId(), VaultAccess.Role.OWNER); return Response.created(URI.create(".")).contentLocation(URI.create(".")).entity(VaultDto.fromEntity(vault)).type(MediaType.APPLICATION_JSON).build(); } else { @@ -496,18 +499,18 @@ public Response claimOwnership(@PathParam("vaultId") UUID vaultId, @FormParam("p throw new BadRequestException("Invalid proof of ownership", e); } - Optional existingAccess = VaultAccess.findByIdOptional(new VaultAccess.Id(vaultId, currentUser.getId())); + Optional existingAccess = vaultAccessRepo.findByIdOptional(new VaultAccess.Id(vaultId, currentUser.getId())); if (existingAccess.isPresent()) { var access = existingAccess.get(); - access.role = VaultAccess.Role.OWNER; - access.persist(); + access.setRole(VaultAccess.Role.OWNER); + vaultAccessRepo.persist(access); vaultMemberUpdatedEventRepo.log(currentUser.getId(), vaultId, currentUser.getId(), VaultAccess.Role.OWNER); } else { var access = new VaultAccess(); - access.vault = vault; - access.authority = currentUser; - access.role = VaultAccess.Role.OWNER; - access.persist(); + access.setVault(vault); + access.setAuthority(currentUser); + access.setRole(VaultAccess.Role.OWNER); + vaultAccessRepo.persist(access); vaultMemberAddedEventRepo.log(currentUser.getId(), vaultId, currentUser.getId(), VaultAccess.Role.OWNER); } 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 132040b84..e9968d791 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,5 @@ package org.cryptomator.hub.entities; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.panache.common.Parameters; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import jakarta.persistence.EmbeddedId; @@ -17,7 +15,6 @@ import java.io.Serializable; import java.util.Objects; import java.util.UUID; -import java.util.stream.Stream; @Entity @Table(name = "vault_access") @@ -29,24 +26,24 @@ INNER JOIN FETCH va.authority WHERE va.id.vaultId = :vaultId """) -public class VaultAccess extends PanacheEntityBase { +public class VaultAccess { @EmbeddedId - public VaultAccess.Id id = new VaultAccess.Id(); + VaultAccess.Id id = new VaultAccess.Id(); @ManyToOne @MapsId("vaultId") @JoinColumn(name = "vault_id") - public Vault vault; + Vault vault; @ManyToOne @MapsId("authorityId") @JoinColumn(name = "authority_id") - public Authority authority; + Authority authority; @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) - public Role role; + Role role; public enum Role { /** @@ -60,18 +57,46 @@ public enum Role { OWNER } - public static Stream forVault(UUID vaultId) { - return find("#VaultAccess.forVault", Parameters.with("vaultId", vaultId)).stream(); + public Id getId() { + return id; + } + + public void setId(Id id) { + this.id = id; + } + + public Vault getVault() { + return vault; + } + + public void setVault(Vault vault) { + this.vault = vault; + } + + public Authority getAuthority() { + return authority; + } + + public void setAuthority(Authority authority) { + this.authority = authority; + } + + public Role getRole() { + return role; + } + + public void setRole(Role role) { + this.role = role; } @Embeddable public static class Id implements Serializable { @Column(name = "vault_id") - public UUID vaultId; + UUID vaultId; @Column(name = "authority_id") - public String authorityId; + String authorityId; public Id(UUID vaultId, String authorityId) { this.vaultId = vaultId; @@ -81,6 +106,22 @@ public Id(UUID vaultId, String authorityId) { public Id() { } + public UUID getVaultId() { + return vaultId; + } + + public void setVaultId(UUID vaultId) { + this.vaultId = vaultId; + } + + public String getAuthorityId() { + return authorityId; + } + + public void setAuthorityId(String authorityId) { + this.authorityId = authorityId; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/VaultAccessRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/VaultAccessRepository.java new file mode 100644 index 000000000..6023b67af --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/VaultAccessRepository.java @@ -0,0 +1,17 @@ +package org.cryptomator.hub.entities; + +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import io.quarkus.panache.common.Parameters; +import jakarta.enterprise.context.ApplicationScoped; + +import java.util.UUID; +import java.util.stream.Stream; + +@ApplicationScoped +public class VaultAccessRepository implements PanacheRepositoryBase { + + public Stream forVault(UUID vaultId) { + return find("#VaultAccess.forVault", Parameters.with("vaultId", vaultId)).stream(); + } + +} From ecbddbfdcfa047f2da8dae30a67a442c8d33e414 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 22 Mar 2024 16:17:27 +0100 Subject: [PATCH 39/54] move repository to entities as inner static class --- .../org/cryptomator/hub/RemoteUserPuller.java | 9 ++-- .../cryptomator/hub/api/AuditLogResource.java | 3 +- .../hub/api/AuthorityResource.java | 4 +- .../cryptomator/hub/api/BillingResource.java | 8 +-- .../cryptomator/hub/api/DeviceResource.java | 21 ++++---- .../cryptomator/hub/api/GroupsResource.java | 8 +-- .../cryptomator/hub/api/UsersResource.java | 16 +++--- .../cryptomator/hub/api/VaultResource.java | 54 +++++++++---------- .../cryptomator/hub/entities/AccessToken.java | 20 +++++++ .../hub/entities/AccessTokenRepository.java | 25 --------- .../cryptomator/hub/entities/Authority.java | 16 ++++++ .../hub/entities/AuthorityRepository.java | 21 -------- .../org/cryptomator/hub/entities/Device.java | 18 ++++++- .../hub/entities/DeviceRepository.java | 27 ---------- .../hub/entities/EffectiveVaultAccess.java | 35 ++++++++++++ .../EffectiveVaultAccessRepository.java | 41 -------------- .../org/cryptomator/hub/entities/Group.java | 6 +++ .../hub/entities/GroupRepository.java | 8 --- .../hub/entities/LegacyAccessToken.java | 19 ++++++- .../entities/LegacyAccessTokenRepository.java | 23 -------- .../hub/entities/LegacyDevice.java | 7 ++- .../hub/entities/LegacyDeviceRepository.java | 10 ---- .../cryptomator/hub/entities/Settings.java | 8 +++ .../hub/entities/SettingsRepository.java | 16 ------ .../org/cryptomator/hub/entities/User.java | 20 +++++++ .../hub/entities/UserRepository.java | 24 --------- .../org/cryptomator/hub/entities/Vault.java | 20 +++++++ .../cryptomator/hub/entities/VaultAccess.java | 12 +++++ .../hub/entities/VaultAccessRepository.java | 17 ------ .../hub/entities/VaultRepository.java | 26 --------- .../hub/entities/events/AuditEvent.java | 22 ++++++++ .../entities/events/AuditEventRepository.java | 29 ---------- .../events/DeviceRegisteredEvent.java | 15 ++++++ .../DeviceRegisteredEventRepository.java | 21 -------- .../entities/events/DeviceRemovedEvent.java | 15 ++++++ .../events/DeviceRemovedEventRepository.java | 19 ------- .../events/VaultAccessGrantedEvent.java | 15 ++++++ .../VaultAccessGrantedEventRepository.java | 21 -------- .../entities/events/VaultCreatedEvent.java | 15 ++++++ .../events/VaultCreatedEventRepository.java | 22 -------- .../events/VaultKeyRetrievedEvent.java | 14 +++++ .../VaultKeyRetrievedEventRepository.java | 20 ------- .../events/VaultMemberAddedEvent.java | 15 ++++++ .../VaultMemberAddedEventRepository.java | 23 -------- .../events/VaultMemberRemovedEvent.java | 15 ++++++ .../VaultMemberRemovedEventRepository.java | 21 -------- .../events/VaultMemberUpdatedEvent.java | 16 ++++++ .../VaultMemberUpdatedEventRepository.java | 24 --------- .../events/VaultOwnershipClaimedEvent.java | 13 +++++ .../VaultOwnershipClaimedEventRepository.java | 20 ------- .../entities/events/VaultUpdatedEvent.java | 17 ++++++ .../events/VaultUpdatedEventRepository.java | 23 -------- .../hub/filters/VaultRoleFilter.java | 8 +-- .../hub/license/LicenseHolder.java | 6 +-- .../cryptomator/hub/RemoteUserPullerTest.java | 7 +-- .../cryptomator/hub/api/VaultResourceIT.java | 4 +- .../cryptomator/hub/entities/EntityIT.java | 4 +- .../hub/filters/VaultRoleFilterTest.java | 7 ++- .../hub/license/LicenseHolderTest.java | 13 +++-- 59 files changed, 423 insertions(+), 583 deletions(-) delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/AccessTokenRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/AuthorityRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/DeviceRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccessRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/GroupRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessTokenRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/LegacyDeviceRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/SettingsRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/UserRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/VaultAccessRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/VaultRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/AuditEventRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEventRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEventRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultAccessGrantedEventRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEventRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEventRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEventRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEventRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEventRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEventRepository.java delete mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEventRepository.java diff --git a/backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java b/backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java index 7ca9922ce..93aa1c864 100644 --- a/backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java +++ b/backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java @@ -5,11 +5,8 @@ import jakarta.inject.Inject; import jakarta.transaction.Transactional; import org.cryptomator.hub.entities.Authority; -import org.cryptomator.hub.entities.AuthorityRepository; import org.cryptomator.hub.entities.Group; -import org.cryptomator.hub.entities.GroupRepository; import org.cryptomator.hub.entities.User; -import org.cryptomator.hub.entities.UserRepository; import java.util.HashSet; import java.util.Map; @@ -21,11 +18,11 @@ public class RemoteUserPuller { @Inject - AuthorityRepository authorityRepo; + Authority.Repository authorityRepo; @Inject - GroupRepository groupRepo; + Group.Repository groupRepo; @Inject - UserRepository userRepo; + User.Repository userRepo; @Inject RemoteUserProvider remoteUserProvider; 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 f158e0c94..eefa03756 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -15,7 +15,6 @@ import org.cryptomator.hub.entities.Device; import org.cryptomator.hub.entities.VaultAccess; import org.cryptomator.hub.entities.events.AuditEvent; -import org.cryptomator.hub.entities.events.AuditEventRepository; import org.cryptomator.hub.entities.events.DeviceRegisteredEvent; import org.cryptomator.hub.entities.events.DeviceRemovedEvent; import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent; @@ -40,7 +39,7 @@ public class AuditLogResource { @Inject - AuditEventRepository auditEventRepo; + AuditEvent.Repository auditEventRepo; @Inject LicenseHolder license; 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 6287f4d06..f33b5c8bd 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java @@ -8,7 +8,7 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; -import org.cryptomator.hub.entities.AuthorityRepository; +import org.cryptomator.hub.entities.Authority; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.jboss.resteasy.reactive.NoCache; @@ -20,7 +20,7 @@ public class AuthorityResource { @Inject - AuthorityRepository authorityRepo; + Authority.Repository 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 e2e5e80e4..71b79da10 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/BillingResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/BillingResource.java @@ -14,8 +14,8 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; -import org.cryptomator.hub.entities.SettingsRepository; +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; @@ -30,9 +30,9 @@ public class BillingResource { @Inject LicenseHolder licenseHolder; @Inject - EffectiveVaultAccessRepository effectiveVaultAccessRepo; + EffectiveVaultAccess.Repository effectiveVaultAccessRepo; @Inject - SettingsRepository settingsRepo; + Settings.Repository settingsRepo; @GET @Path("/") 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 1430827a5..bf5f36028 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java @@ -21,14 +21,11 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.Device; -import org.cryptomator.hub.entities.DeviceRepository; import org.cryptomator.hub.entities.LegacyAccessToken; -import org.cryptomator.hub.entities.LegacyAccessTokenRepository; -import org.cryptomator.hub.entities.LegacyDeviceRepository; +import org.cryptomator.hub.entities.LegacyDevice; import org.cryptomator.hub.entities.User; -import org.cryptomator.hub.entities.UserRepository; -import org.cryptomator.hub.entities.events.DeviceRegisteredEventRepository; -import org.cryptomator.hub.entities.events.DeviceRemovedEventRepository; +import org.cryptomator.hub.entities.events.DeviceRegisteredEvent; +import org.cryptomator.hub.entities.events.DeviceRemovedEvent; import org.cryptomator.hub.validation.NoHtmlOrScriptChars; import org.cryptomator.hub.validation.OnlyBase64Chars; import org.cryptomator.hub.validation.ValidId; @@ -54,17 +51,17 @@ public class DeviceResource { private static final Logger LOG = Logger.getLogger(DeviceResource.class); @Inject - DeviceRegisteredEventRepository deviceRegisteredEventRepo; + DeviceRegisteredEvent.Repository deviceRegisteredEventRepo; @Inject - DeviceRemovedEventRepository deviceRemovedEventRepo; + DeviceRemovedEvent.Repository deviceRemovedEventRepo; @Inject - UserRepository userRepo; + User.Repository userRepo; @Inject - DeviceRepository deviceRepo; + Device.Repository deviceRepo; @Inject - LegacyAccessTokenRepository legacyAccessTokenRepo; + LegacyAccessToken.Repository legacyAccessTokenRepo; @Inject - LegacyDeviceRepository legacyDeviceRepo; + LegacyDevice.Repository legacyDeviceRepo; @Inject JsonWebToken jwt; 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 76cffee5d..78db054a5 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java @@ -7,8 +7,8 @@ import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; -import org.cryptomator.hub.entities.GroupRepository; -import org.cryptomator.hub.entities.UserRepository; +import org.cryptomator.hub.entities.Group; +import org.cryptomator.hub.entities.User; import org.cryptomator.hub.validation.ValidId; import org.eclipse.microprofile.openapi.annotations.Operation; @@ -18,9 +18,9 @@ public class GroupsResource { @Inject - UserRepository userRepo; + User.Repository userRepo; @Inject - GroupRepository groupRepo; + Group.Repository groupRepo; @GET @Path("/") 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 78eba7091..32639ba76 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java @@ -16,14 +16,10 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.AccessToken; -import org.cryptomator.hub.entities.AccessTokenRepository; import org.cryptomator.hub.entities.Device; -import org.cryptomator.hub.entities.DeviceRepository; import org.cryptomator.hub.entities.User; -import org.cryptomator.hub.entities.UserRepository; import org.cryptomator.hub.entities.Vault; -import org.cryptomator.hub.entities.VaultRepository; -import org.cryptomator.hub.entities.events.VaultAccessGrantedEventRepository; +import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent; import org.eclipse.microprofile.jwt.JsonWebToken; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; @@ -43,15 +39,15 @@ public class UsersResource { @Inject - AccessTokenRepository accessTokenRepo; + AccessToken.Repository accessTokenRepo; @Inject - VaultAccessGrantedEventRepository vaultAccessGrantedEventRepo; + VaultAccessGrantedEvent.Repository vaultAccessGrantedEventRepo; @Inject - UserRepository userRepo; + User.Repository userRepo; @Inject - DeviceRepository deviceRepo; + Device.Repository deviceRepo; @Inject - VaultRepository vaultRepo; + Vault.Repository vaultRepo; @Inject JsonWebToken jwt; 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 001e26e9b..08cb98a5f 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -31,27 +31,21 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.AccessToken; -import org.cryptomator.hub.entities.AccessTokenRepository; import org.cryptomator.hub.entities.Authority; -import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; +import org.cryptomator.hub.entities.EffectiveVaultAccess; import org.cryptomator.hub.entities.Group; -import org.cryptomator.hub.entities.GroupRepository; -import org.cryptomator.hub.entities.LegacyAccessTokenRepository; +import org.cryptomator.hub.entities.LegacyAccessToken; import org.cryptomator.hub.entities.User; -import org.cryptomator.hub.entities.UserRepository; import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.VaultAccess; -import org.cryptomator.hub.entities.VaultAccessRepository; -import org.cryptomator.hub.entities.VaultRepository; -import org.cryptomator.hub.entities.events.VaultAccessGrantedEventRepository; -import org.cryptomator.hub.entities.events.VaultCreatedEventRepository; +import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent; +import org.cryptomator.hub.entities.events.VaultCreatedEvent; import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; -import org.cryptomator.hub.entities.events.VaultKeyRetrievedEventRepository; -import org.cryptomator.hub.entities.events.VaultMemberAddedEventRepository; -import org.cryptomator.hub.entities.events.VaultMemberRemovedEventRepository; -import org.cryptomator.hub.entities.events.VaultMemberUpdatedEventRepository; -import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEventRepository; -import org.cryptomator.hub.entities.events.VaultUpdatedEventRepository; +import org.cryptomator.hub.entities.events.VaultMemberAddedEvent; +import org.cryptomator.hub.entities.events.VaultMemberRemovedEvent; +import org.cryptomator.hub.entities.events.VaultMemberUpdatedEvent; +import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEvent; +import org.cryptomator.hub.entities.events.VaultUpdatedEvent; import org.cryptomator.hub.filters.ActiveLicense; import org.cryptomator.hub.filters.VaultRole; import org.cryptomator.hub.license.LicenseHolder; @@ -78,35 +72,35 @@ public class VaultResource { @Inject - AccessTokenRepository accessTokenRepo; + AccessToken.Repository accessTokenRepo; @Inject - VaultAccessGrantedEventRepository vaultAccessGrantedEventRepo; + VaultAccessGrantedEvent.Repository vaultAccessGrantedEventRepo; @Inject - VaultCreatedEventRepository vaultCreatedEventRepo; + VaultCreatedEvent.Repository vaultCreatedEventRepo; @Inject - VaultKeyRetrievedEventRepository vaultKeyRetrievedEventRepo; + VaultKeyRetrievedEvent.Repository vaultKeyRetrievedEventRepo; @Inject - VaultMemberAddedEventRepository vaultMemberAddedEventRepo; + VaultMemberAddedEvent.Repository vaultMemberAddedEventRepo; @Inject - VaultMemberRemovedEventRepository vaultMemberRemovedEventRepo; + VaultMemberRemovedEvent.Repository vaultMemberRemovedEventRepo; @Inject - VaultMemberUpdatedEventRepository vaultMemberUpdatedEventRepo; + VaultMemberUpdatedEvent.Repository vaultMemberUpdatedEventRepo; @Inject - VaultOwnershipClaimedEventRepository vaultOwnershipClaimedEventRepo; + VaultOwnershipClaimedEvent.Repository vaultOwnershipClaimedEventRepo; @Inject - VaultUpdatedEventRepository vaultUpdatedEventRepo; + VaultUpdatedEvent.Repository vaultUpdatedEventRepo; @Inject - GroupRepository groupRepo; + Group.Repository groupRepo; @Inject - UserRepository userRepo; + User.Repository userRepo; @Inject - EffectiveVaultAccessRepository effectiveVaultAccessRepo; + EffectiveVaultAccess.Repository effectiveVaultAccessRepo; @Inject - LegacyAccessTokenRepository legacyAccessTokenRepo; + LegacyAccessToken.Repository legacyAccessTokenRepo; @Inject - VaultRepository vaultRepo; + Vault.Repository vaultRepo; @Inject - VaultAccessRepository vaultAccessRepo; + VaultAccess.Repository vaultAccessRepo; @Inject JsonWebToken jwt; 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 5a996eb0f..bbb5c32fe 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java @@ -1,5 +1,8 @@ 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; import jakarta.persistence.Embeddable; @@ -9,6 +12,7 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.MapsId; import jakarta.persistence.NamedQuery; +import jakarta.persistence.NoResultException; import jakarta.persistence.Table; import java.io.Serializable; @@ -156,4 +160,20 @@ public String toString() { '}'; } } + + @ApplicationScoped + public static class Repository implements PanacheRepositoryBase { + + public AccessToken unlock(UUID vaultId, String userId) { + try { + return find("#AccessToken.get", Parameters.with("vaultId", vaultId).and("userId", userId)).firstResult(); + } catch (NoResultException e) { + return null; + } + } + + public void deleteByUser(String userId) { + delete("#AccessToken.deleteByUser", Parameters.with("userId", userId)); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AccessTokenRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/AccessTokenRepository.java deleted file mode 100644 index 029489283..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/AccessTokenRepository.java +++ /dev/null @@ -1,25 +0,0 @@ -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.NoResultException; - -import java.util.UUID; - -@ApplicationScoped -public class AccessTokenRepository implements PanacheRepositoryBase { - - public AccessToken unlock(UUID vaultId, String userId) { - try { - return find("#AccessToken.get", Parameters.with("vaultId", vaultId).and("userId", userId)).firstResult(); - } catch (NoResultException e) { - return null; - } - } - - public void deleteByUser(String userId) { - delete("#AccessToken.deleteByUser", Parameters.with("userId", userId)); - } - -} diff --git a/backend/src/main/java/org/cryptomator/hub/entities/Authority.java b/backend/src/main/java/org/cryptomator/hub/entities/Authority.java index f63495e85..c96cf659f 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Authority.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Authority.java @@ -1,5 +1,8 @@ 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.DiscriminatorColumn; import jakarta.persistence.Entity; @@ -9,7 +12,9 @@ import jakarta.persistence.NamedQuery; import jakarta.persistence.Table; +import java.util.List; import java.util.Objects; +import java.util.stream.Stream; @Entity @Table(name = "authority") @@ -74,4 +79,15 @@ public int hashCode() { return Objects.hash(id, name); } + @ApplicationScoped + public static class Repository implements PanacheRepositoryBase { + + public Stream byName(String name) { + return find("#Authority.byName", Parameters.with("name", '%' + name.toLowerCase() + '%')).stream(); + } + + public Stream findAllInList(List ids) { + return find("#Authority.allInList", Parameters.with("ids", ids)).stream(); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AuthorityRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/AuthorityRepository.java deleted file mode 100644 index e1b3e1eeb..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/AuthorityRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.cryptomator.hub.entities; - -import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; -import jakarta.enterprise.context.ApplicationScoped; - -import java.util.List; -import java.util.stream.Stream; - -@ApplicationScoped -public class AuthorityRepository implements PanacheRepositoryBase { - - public Stream byName(String name) { - return find("#Authority.byName", Parameters.with("name", '%' + name.toLowerCase() + '%')).stream(); - } - - public Stream findAllInList(List ids) { - return find("#Authority.allInList", Parameters.with("ids", ids)).stream(); - } - -} 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 f0f854228..6d16963f2 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,8 @@ package org.cryptomator.hub.entities; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; +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; import jakarta.persistence.EnumType; @@ -149,4 +150,19 @@ public int hashCode() { return Objects.hash(id, owner, name, type, publickey, userPrivateKey, creationTime); } + @ApplicationScoped + 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(); + } + + public Stream findAllInList(List ids) { + return find("#Device.allInList", Parameters.with("ids", ids)).stream(); + } + + public void deleteByOwner(String userId) { + delete("#Device.deleteByOwner", Parameters.with("userId", userId)); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/DeviceRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/DeviceRepository.java deleted file mode 100644 index 282c5b8c0..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/DeviceRepository.java +++ /dev/null @@ -1,27 +0,0 @@ -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.NoResultException; - -import java.util.List; -import java.util.stream.Stream; - -@ApplicationScoped -public class DeviceRepository implements PanacheRepositoryBase { - - public Device findByIdAndUser(String deviceId, String userId) throws NoResultException { - return find("#Device.findByIdAndOwner", Parameters.with("deviceId", deviceId).and("userId", userId)).singleResult(); - } - - public Stream findAllInList(List ids) { - return find("#Device.allInList", Parameters.with("ids", ids)).stream(); - } - - public void deleteByOwner(String userId) { - delete("#Device.deleteByOwner", Parameters.with("userId", userId)); - } - - -} 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 6755162b0..73d89034c 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java @@ -1,5 +1,8 @@ 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; import jakarta.persistence.EmbeddedId; @@ -11,8 +14,11 @@ import org.hibernate.annotations.Immutable; import java.io.Serializable; +import java.util.Collection; +import java.util.List; import java.util.Objects; import java.util.UUID; +import java.util.stream.Collectors; @Entity @Immutable @@ -139,4 +145,33 @@ public String toString() { } } + @ApplicationScoped + public static class Repository implements PanacheRepositoryBase { + + public boolean isUserOccupyingSeat(String userId) { + return count("#EffectiveVaultAccess.countSeatsOccupiedBySingleUser", Parameters.with("userId", userId)) > 0; + } + + public long countSeatsOccupiedByUsers(List userIds) { + return count("#EffectiveVaultAccess.countSeatsOccupiedByUsers", Parameters.with("userIds", userIds)); + } + + public long countSeatOccupyingUsers() { + return count("#EffectiveVaultAccess.countSeatOccupyingUsers"); + } + + public long countSeatOccupyingUsersWithAccessToken() { + return count("#EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken"); + } + + public long countSeatOccupyingUsersOfGroup(String groupId) { + return count("#EffectiveVaultAccess.countSeatOccupyingUsersOfGroup", Parameters.with("groupId", groupId)); + } + + public Collection listRoles(UUID vaultId, String authorityId) { + return find("#EffectiveVaultAccess.findByAuthorityAndVault", Parameters.with("vaultId", vaultId).and("authorityId", authorityId)).stream() + .map(eva -> eva.getId().getRole()) + .collect(Collectors.toUnmodifiableSet()); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccessRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccessRepository.java deleted file mode 100644 index e82037aa3..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccessRepository.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.cryptomator.hub.entities; - -import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; -import jakarta.enterprise.context.ApplicationScoped; - -import java.util.Collection; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -@ApplicationScoped -public class EffectiveVaultAccessRepository implements PanacheRepositoryBase { - - - public boolean isUserOccupyingSeat(String userId) { - return count("#EffectiveVaultAccess.countSeatsOccupiedBySingleUser", Parameters.with("userId", userId)) > 0; - } - - public long countSeatsOccupiedByUsers(List userIds) { - return count("#EffectiveVaultAccess.countSeatsOccupiedByUsers", Parameters.with("userIds", userIds)); - } - - public long countSeatOccupyingUsers() { - return count("#EffectiveVaultAccess.countSeatOccupyingUsers"); - } - - public long countSeatOccupyingUsersWithAccessToken() { - return count("#EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken"); - } - - public long countSeatOccupyingUsersOfGroup(String groupId) { - return count("#EffectiveVaultAccess.countSeatOccupyingUsersOfGroup", Parameters.with("groupId", groupId)); - } - - public Collection listRoles(UUID vaultId, String authorityId) { - return find("#EffectiveVaultAccess.findByAuthorityAndVault", Parameters.with("vaultId", vaultId).and("authorityId", authorityId)).stream() - .map(eva -> eva.getId().getRole()) - .collect(Collectors.toUnmodifiableSet()); - } -} 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 55f5c1065..aeb108fb5 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Group.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Group.java @@ -1,5 +1,7 @@ package org.cryptomator.hub.entities; +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; @@ -29,4 +31,8 @@ public Set getMembers() { public void setMembers(Set members) { this.members = members; } + + @ApplicationScoped + public static class Repository implements PanacheRepositoryBase { + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/GroupRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/GroupRepository.java deleted file mode 100644 index f562816bd..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/GroupRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.cryptomator.hub.entities; - -import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import jakarta.enterprise.context.ApplicationScoped; - -@ApplicationScoped -public class GroupRepository implements PanacheRepositoryBase { -} diff --git a/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java b/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java index f74410d44..56b25aa92 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java @@ -1,15 +1,16 @@ package org.cryptomator.hub.entities; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import jakarta.persistence.EmbeddedId; import jakarta.persistence.Entity; import jakarta.persistence.NamedQuery; -import jakarta.persistence.NoResultException; import jakarta.persistence.Table; import java.io.Serializable; +import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.stream.Stream; @@ -132,4 +133,18 @@ public String toString() { '}'; } } + + @ApplicationScoped + @Deprecated + public static class Repository implements PanacheRepositoryBase { + + public LegacyAccessToken unlock(UUID vaultId, String deviceId, String userId) { + return find("#LegacyAccessToken.get", Map.of("deviceId", deviceId, "vaultId", vaultId, "userId", userId)) + .firstResult(); + } + + public Stream getByDeviceAndOwner(String deviceId, String userId) { + return stream("#LegacyAccessToken.getByDevice", Map.of("deviceId", deviceId, "userId", userId)); + } + } } \ No newline at end of file diff --git a/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessTokenRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessTokenRepository.java deleted file mode 100644 index a245a5418..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessTokenRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.cryptomator.hub.entities; - -import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import jakarta.enterprise.context.ApplicationScoped; - -import java.util.Map; -import java.util.UUID; -import java.util.stream.Stream; - -@ApplicationScoped -@Deprecated -public class LegacyAccessTokenRepository implements PanacheRepositoryBase { - - public LegacyAccessToken unlock(UUID vaultId, String deviceId, String userId) { - return find("#LegacyAccessToken.get", Map.of("deviceId", deviceId, "vaultId", vaultId, "userId", userId)) - .firstResult(); - } - - public Stream getByDeviceAndOwner(String deviceId, String userId) { - return stream("#LegacyAccessToken.getByDevice", Map.of("deviceId", deviceId, "userId", userId)); - } - -} 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 8369b172c..97d1064e0 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/LegacyDevice.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/LegacyDevice.java @@ -1,6 +1,7 @@ package org.cryptomator.hub.entities; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -36,4 +37,8 @@ public void setOwnerId(String ownerId) { // Further attributes omitted, as they are no longer used. The above ones are exceptions, as they are referenced via JPQL for joining. + @ApplicationScoped + @Deprecated + public static class Repository implements PanacheRepositoryBase { + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/LegacyDeviceRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/LegacyDeviceRepository.java deleted file mode 100644 index 80841b8a2..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/LegacyDeviceRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.cryptomator.hub.entities; - -import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import jakarta.enterprise.context.ApplicationScoped; -import org.cryptomator.hub.entities.LegacyDevice; - -@ApplicationScoped -@Deprecated -public class LegacyDeviceRepository implements PanacheRepositoryBase { -} 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 e2eaf8545..0f9415ebd 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Settings.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Settings.java @@ -1,5 +1,7 @@ package org.cryptomator.hub.entities; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -71,5 +73,11 @@ public int hashCode() { return Objects.hash(id, hubId, licenseKey); } + @ApplicationScoped + public static class Repository implements PanacheRepository { + public Settings get() { + return Objects.requireNonNull(findById((long) SINGLETON_ID), "Settings not initialized"); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/SettingsRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/SettingsRepository.java deleted file mode 100644 index 7fbbaf327..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/SettingsRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.cryptomator.hub.entities; - -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; - -import java.util.Objects; - -import static org.cryptomator.hub.entities.Settings.SINGLETON_ID; - -@ApplicationScoped -public class SettingsRepository implements PanacheRepository { - - public Settings get() { - return Objects.requireNonNull(findById((long) SINGLETON_ID), "Settings not initialized"); - } -} 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 07cd44a5b..78d87ce25 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/User.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/User.java @@ -1,5 +1,8 @@ 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.DiscriminatorValue; import jakarta.persistence.Entity; @@ -11,6 +14,8 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; @Entity @Table(name = "user_details") @@ -133,4 +138,19 @@ public int hashCode() { return Objects.hash(id, pictureUrl, email, publicKey, privateKey, setupCode); } + @ApplicationScoped + public static class Repository implements PanacheRepositoryBase { + + public Stream findRequiringAccessGrant(UUID vaultId) { + return find("#User.requiringAccessGrant", Parameters.with("vaultId", vaultId)).stream(); + } + + public long countEffectiveGroupUsers(String groupdId) { + return count("#User.countEffectiveGroupUsers", Parameters.with("groupId", groupdId)); + } + + public Stream getEffectiveGroupUsers(String groupdId) { + return find("#User.getEffectiveGroupUsers", Parameters.with("groupId", groupdId)).stream(); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/UserRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/UserRepository.java deleted file mode 100644 index ba5a2237c..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/UserRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.cryptomator.hub.entities; - -import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; -import jakarta.enterprise.context.ApplicationScoped; - -import java.util.UUID; -import java.util.stream.Stream; - -@ApplicationScoped -public class UserRepository implements PanacheRepositoryBase { - - public Stream findRequiringAccessGrant(UUID vaultId) { - return find("#User.requiringAccessGrant", Parameters.with("vaultId", vaultId)).stream(); - } - - public long countEffectiveGroupUsers(String groupdId) { - return count("#User.countEffectiveGroupUsers", Parameters.with("groupId", groupdId)); - } - - public Stream getEffectiveGroupUsers(String groupdId) { - return find("#User.getEffectiveGroupUsers", Parameters.with("groupId", groupdId)).stream(); - } -} 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 cad96b99f..32ea9e5d2 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Vault.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Vault.java @@ -1,5 +1,8 @@ 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; import jakarta.persistence.FetchType; @@ -20,11 +23,13 @@ import java.time.Instant; import java.util.Base64; import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.Stream; @Entity @Table(name = "vault") @@ -259,4 +264,19 @@ public String toString() { '}'; } + @ApplicationScoped + public static class Repository implements PanacheRepositoryBase { + + public Stream findAccessibleByUser(String userId) { + return find("#Vault.accessibleByUser", Parameters.with("userId", userId)).stream(); + } + + public Stream findAccessibleByUser(String userId, VaultAccess.Role role) { + return find("#Vault.accessibleByUserAndRole", Parameters.with("userId", userId).and("role", role)).stream(); + } + + public Stream findAllInList(List ids) { + return find("#Vault.allInList", Parameters.with("ids", ids)).stream(); + } + } } 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 e9968d791..979738e64 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/VaultAccess.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/VaultAccess.java @@ -1,5 +1,8 @@ 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; import jakarta.persistence.EmbeddedId; @@ -15,6 +18,7 @@ import java.io.Serializable; import java.util.Objects; import java.util.UUID; +import java.util.stream.Stream; @Entity @Table(name = "vault_access") @@ -144,4 +148,12 @@ public String toString() { '}'; } } + + @ApplicationScoped + public static class Repository implements PanacheRepositoryBase { + + public Stream forVault(UUID vaultId) { + return find("#VaultAccess.forVault", Parameters.with("vaultId", vaultId)).stream(); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/VaultAccessRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/VaultAccessRepository.java deleted file mode 100644 index 6023b67af..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/VaultAccessRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.cryptomator.hub.entities; - -import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; -import jakarta.enterprise.context.ApplicationScoped; - -import java.util.UUID; -import java.util.stream.Stream; - -@ApplicationScoped -public class VaultAccessRepository implements PanacheRepositoryBase { - - public Stream forVault(UUID vaultId) { - return find("#VaultAccess.forVault", Parameters.with("vaultId", vaultId)).stream(); - } - -} diff --git a/backend/src/main/java/org/cryptomator/hub/entities/VaultRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/VaultRepository.java deleted file mode 100644 index 47d168a65..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/VaultRepository.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.cryptomator.hub.entities; - -import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import io.quarkus.panache.common.Parameters; -import jakarta.enterprise.context.ApplicationScoped; - -import java.util.List; -import java.util.UUID; -import java.util.stream.Stream; - -@ApplicationScoped -public class VaultRepository implements PanacheRepositoryBase { - - public Stream findAccessibleByUser(String userId) { - return find("#Vault.accessibleByUser", Parameters.with("userId", userId)).stream(); - } - - public Stream findAccessibleByUser(String userId, VaultAccess.Role role) { - return find("#Vault.accessibleByUserAndRole", Parameters.with("userId", userId).and("role", role)).stream(); - } - - public Stream findAllInList(List ids) { - return find("#Vault.allInList", Parameters.with("ids", ids)).stream(); - } - -} 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 bda69a76d..b42b51f72 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 @@ -1,5 +1,9 @@ package org.cryptomator.hub.entities.events; +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; import jakarta.persistence.Entity; @@ -14,6 +18,7 @@ import java.time.Instant; import java.util.Objects; +import java.util.stream.Stream; @Entity @Table(name = "audit_event") @@ -97,4 +102,21 @@ public int hashCode() { return Objects.hash(id, timestamp); } + + @ApplicationScoped + public static class Repository implements PanacheRepository { + + public Stream findAllInPeriod(Instant startDate, Instant endDate, long paginationId, boolean ascending, int pageSize) { + var parameters = Parameters.with("startDate", startDate).and("endDate", endDate).and("paginationId", paginationId); + + final PanacheQuery query; + if (ascending) { + query = find("#AuditEvent.listAllInPeriodAfterId", parameters); + } else { + query = find("#AuditEvent.listAllInPeriodBeforeId", parameters); + } + query.page(0, pageSize); + return query.stream(); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEventRepository.java deleted file mode 100644 index 4960fe4a2..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEventRepository.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.cryptomator.hub.entities.events; - -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 java.time.Instant; -import java.util.stream.Stream; - -@ApplicationScoped -public class AuditEventRepository implements PanacheRepository { - - public Stream findAllInPeriod(Instant startDate, Instant endDate, long paginationId, boolean ascending, int pageSize) { - var parameters = Parameters.with("startDate", startDate).and("endDate", endDate).and("paginationId", paginationId); - - final PanacheQuery query; - if (ascending) { - query = find("#AuditEvent.listAllInPeriodAfterId", parameters); - } else { - query = find("#AuditEvent.listAllInPeriodBeforeId", parameters); - } - query.page(0, pageSize); - return query.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 fae708b7c..328f7b2da 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 @@ -1,5 +1,7 @@ package org.cryptomator.hub.entities.events; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -80,4 +82,17 @@ public int hashCode() { return Objects.hash(id, registeredBy, deviceId, deviceName, deviceType); } + @ApplicationScoped + public static class Repository implements PanacheRepository { + + public void log(String registeredBy, String deviceId, String deviceName, Device.Type deviceType) { + var event = new DeviceRegisteredEvent(); + event.setTimestamp(Instant.now()); + event.setRegisteredBy(registeredBy); + event.setDeviceId(deviceId); + event.setDeviceName(deviceName); + event.setDeviceType(deviceType); + persist(event); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEventRepository.java deleted file mode 100644 index a81559c5f..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRegisteredEventRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.cryptomator.hub.entities.events; - -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; -import org.cryptomator.hub.entities.Device; - -import java.time.Instant; - -@ApplicationScoped -public class DeviceRegisteredEventRepository implements PanacheRepository { - - public void log(String registeredBy, String deviceId, String deviceName, Device.Type deviceType) { - var event = new DeviceRegisteredEvent(); - event.setTimestamp(Instant.now()); - event.setRegisteredBy(registeredBy); - event.setDeviceId(deviceId); - event.setDeviceName(deviceName); - event.setDeviceType(deviceType); - persist(event); - } -} 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 2bcd6f21d..5dc2b73e2 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 @@ -1,5 +1,7 @@ package org.cryptomator.hub.entities.events; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -52,4 +54,17 @@ public int hashCode() { return Objects.hash(id, removedBy, deviceId); } + + @ApplicationScoped + public static class Repository implements PanacheRepository { + + public void log(String removedBy, String deviceId) { + var event = new DeviceRemovedEvent(); + event.setTimestamp(Instant.now()); + event.setRemovedBy(removedBy); + event.setDeviceId(deviceId); + persist(event); + } + + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEventRepository.java deleted file mode 100644 index 7b17edc53..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/DeviceRemovedEventRepository.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.cryptomator.hub.entities.events; - -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; - -import java.time.Instant; - -@ApplicationScoped -public class DeviceRemovedEventRepository implements PanacheRepository { - - public void log(String removedBy, String deviceId) { - var event = new DeviceRemovedEvent(); - event.setTimestamp(Instant.now()); - event.setRemovedBy(removedBy); - event.setDeviceId(deviceId); - persist(event); - } - -} 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 217894b59..aa4bbba67 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 @@ -1,5 +1,7 @@ package org.cryptomator.hub.entities.events; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -65,4 +67,17 @@ public int hashCode() { return Objects.hash(id, grantedBy, vaultId, authorityId); } + @ApplicationScoped + public static class Repository implements PanacheRepository { + + public void log(String grantedBy, UUID vaultId, String authorityId) { + var event = new VaultAccessGrantedEvent(); + event.setTimestamp(Instant.now()); + event.setGrantedBy(grantedBy); + event.setVaultId(vaultId); + event.setAuthorityId(authorityId); + persist(event); + } + + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultAccessGrantedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultAccessGrantedEventRepository.java deleted file mode 100644 index 063523fc2..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultAccessGrantedEventRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.cryptomator.hub.entities.events; - -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; - -import java.time.Instant; -import java.util.UUID; - -@ApplicationScoped -public class VaultAccessGrantedEventRepository implements PanacheRepository { - - public void log(String grantedBy, UUID vaultId, String authorityId) { - var event = new VaultAccessGrantedEvent(); - event.setTimestamp(Instant.now()); - event.setGrantedBy(grantedBy); - event.setVaultId(vaultId); - event.setAuthorityId(authorityId); - persist(event); - } - -} 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 f95aa7cad..8b472178c 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 @@ -1,5 +1,7 @@ package org.cryptomator.hub.entities.events; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -77,4 +79,17 @@ public int hashCode() { return Objects.hash(id, createdBy, vaultId, vaultName, vaultDescription); } + @ApplicationScoped + public static class Repository implements PanacheRepository { + + public void log(String createdBy, UUID vaultId, String vaultName, String vaultDescription) { + var event = new VaultCreatedEvent(); + event.setTimestamp(Instant.now()); + event.setCreatedBy(createdBy); + event.setVaultId(vaultId); + event.setVaultName(vaultName); + event.setVaultDescription(vaultDescription); + persist(event); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEventRepository.java deleted file mode 100644 index 6acfb8607..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultCreatedEventRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.cryptomator.hub.entities.events; - -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; - -import java.time.Instant; -import java.util.UUID; - -@ApplicationScoped -public class VaultCreatedEventRepository implements PanacheRepository { - - public void log(String createdBy, UUID vaultId, String vaultName, String vaultDescription) { - var event = new VaultCreatedEvent(); - event.setTimestamp(Instant.now()); - event.setCreatedBy(createdBy); - event.setVaultId(vaultId); - event.setVaultName(vaultName); - event.setVaultDescription(vaultDescription); - persist(event); - } - -} 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 7219505e5..dbef6cfe3 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 @@ -1,5 +1,7 @@ package org.cryptomator.hub.entities.events; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -73,4 +75,16 @@ public enum Result { UNAUTHORIZED } + @ApplicationScoped + public static class Repository implements PanacheRepository { + + public void log(String retrievedBy, UUID vaultId, VaultKeyRetrievedEvent.Result result) { + var event = new VaultKeyRetrievedEvent(); + event.setTimestamp(Instant.now()); + event.setRetrievedBy(retrievedBy); + event.setVaultId(vaultId); + event.setResult(result); + persist(event); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEventRepository.java deleted file mode 100644 index fc4f1391e..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEventRepository.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.cryptomator.hub.entities.events; - -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; - -import java.time.Instant; -import java.util.UUID; - -@ApplicationScoped -public class VaultKeyRetrievedEventRepository implements PanacheRepository { - - public void log(String retrievedBy, UUID vaultId, VaultKeyRetrievedEvent.Result result) { - var event = new VaultKeyRetrievedEvent(); - event.setTimestamp(Instant.now()); - event.setRetrievedBy(retrievedBy); - event.setVaultId(vaultId); - event.setResult(result); - persist(event); - } -} 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 f7a3a94dd..59ccf21c1 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 @@ -1,5 +1,7 @@ package org.cryptomator.hub.entities.events; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -81,4 +83,17 @@ public int hashCode() { return Objects.hash(id, addedBy, vaultId, authorityId, role); } + @ApplicationScoped + public static class Repository implements PanacheRepository { + + public void log(String addedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { + var event = new VaultMemberAddedEvent(); + event.setTimestamp(Instant.now()); + event.setAddedBy(addedBy); + event.setVaultId(vaultId); + event.setAuthorityId(authorityId); + event.setRole(role); + persist(event); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEventRepository.java deleted file mode 100644 index 3ec350015..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberAddedEventRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.cryptomator.hub.entities.events; - -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; -import org.cryptomator.hub.entities.VaultAccess; - -import java.time.Instant; -import java.util.UUID; - -@ApplicationScoped -public class VaultMemberAddedEventRepository implements PanacheRepository { - - - public void log(String addedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { - var event = new VaultMemberAddedEvent(); - event.setTimestamp(Instant.now()); - event.setAddedBy(addedBy); - event.setVaultId(vaultId); - event.setAuthorityId(authorityId); - event.setRole(role); - persist(event); - } -} 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 27285fcf9..cd4647218 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 @@ -1,5 +1,7 @@ package org.cryptomator.hub.entities.events; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -65,4 +67,17 @@ public int hashCode() { return Objects.hash(id, removedBy, vaultId, authorityId); } + @ApplicationScoped + public static class Repository implements PanacheRepository { + + public void log(String removedBy, UUID vaultId, String authorityId) { + var event = new VaultMemberRemovedEvent(); + event.setTimestamp(Instant.now()); + event.setRemovedBy(removedBy); + event.setVaultId(vaultId); + event.setAuthorityId(authorityId); + persist(event); + } + + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEventRepository.java deleted file mode 100644 index 0e5813150..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberRemovedEventRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.cryptomator.hub.entities.events; - -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; - -import java.time.Instant; -import java.util.UUID; - -@ApplicationScoped -public class VaultMemberRemovedEventRepository implements PanacheRepository { - - public void log(String removedBy, UUID vaultId, String authorityId) { - var event = new VaultMemberRemovedEvent(); - event.setTimestamp(Instant.now()); - event.setRemovedBy(removedBy); - event.setVaultId(vaultId); - event.setAuthorityId(authorityId); - persist(event); - } - -} 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 dce832957..06f62d900 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 @@ -1,5 +1,8 @@ package org.cryptomator.hub.entities.events; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -81,4 +84,17 @@ public int hashCode() { return Objects.hash(id, updatedBy, vaultId, authorityId, role); } + @ApplicationScoped + public static class Repository implements PanacheRepository { + + public void log(String updatedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { + var event = new VaultMemberUpdatedEvent(); + event.setTimestamp(Instant.now()); + event.setUpdatedBy(updatedBy); + event.setVaultId(vaultId); + event.setAuthorityId(authorityId); + event.setRole(role); + persist(event); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEventRepository.java deleted file mode 100644 index e67c036e6..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultMemberUpdatedEventRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.cryptomator.hub.entities.events; - -import jakarta.enterprise.context.ApplicationScoped; -import org.cryptomator.hub.entities.VaultAccess; - -import java.time.Instant; -import java.util.UUID; - -import static io.quarkus.hibernate.orm.panache.PanacheEntityBase.persist; - -@ApplicationScoped -public class VaultMemberUpdatedEventRepository { - - public void log(String updatedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { - var event = new VaultMemberUpdatedEvent(); - event.setTimestamp(Instant.now()); - event.setUpdatedBy(updatedBy); - event.setVaultId(vaultId); - event.setAuthorityId(authorityId); - event.setRole(role); - persist(event); - } - -} 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 c9de69769..aad508743 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 @@ -1,5 +1,7 @@ package org.cryptomator.hub.entities.events; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -53,4 +55,15 @@ public int hashCode() { return Objects.hash(id, claimedBy, vaultId); } + @ApplicationScoped + public static class Repository implements PanacheRepository { + + public void log(String claimedBy, UUID vaultId) { + var event = new VaultOwnershipClaimedEvent(); + event.setTimestamp(Instant.now()); + event.setClaimedBy(claimedBy); + event.setVaultId(vaultId); + persist(event); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEventRepository.java deleted file mode 100644 index 276eb6baa..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultOwnershipClaimedEventRepository.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.cryptomator.hub.entities.events; - -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; - -import java.time.Instant; -import java.util.UUID; - -@ApplicationScoped -public class VaultOwnershipClaimedEventRepository implements PanacheRepository { - - public void log(String claimedBy, UUID vaultId) { - var event = new VaultOwnershipClaimedEvent(); - event.setTimestamp(Instant.now()); - event.setClaimedBy(claimedBy); - event.setVaultId(vaultId); - persist(event); - } - -} 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 d1e3c454a..ab983ff5b 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 @@ -1,10 +1,13 @@ package org.cryptomator.hub.entities.events; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import java.time.Instant; import java.util.Objects; import java.util.UUID; @@ -88,4 +91,18 @@ public int hashCode() { return Objects.hash(id, updatedBy, vaultId, vaultName, vaultDescription, vaultArchived); } + @ApplicationScoped + public static class Repository implements PanacheRepository { + + public void log(String updatedBy, UUID vaultId, String vaultName, String vaultDescription, boolean vaultArchived) { + var event = new VaultUpdatedEvent(); + event.setTimestamp(Instant.now()); + event.setUpdatedBy(updatedBy); + event.setVaultId(vaultId); + event.setVaultName(vaultName); + event.setVaultDescription(vaultDescription); + event.setVaultArchived(vaultArchived); + persist(event); + } + } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEventRepository.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEventRepository.java deleted file mode 100644 index 9c80c4f6e..000000000 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultUpdatedEventRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.cryptomator.hub.entities.events; - -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; - -import java.time.Instant; -import java.util.UUID; - -@ApplicationScoped -public class VaultUpdatedEventRepository implements PanacheRepository { - - public void log(String updatedBy, UUID vaultId, String vaultName, String vaultDescription, boolean vaultArchived) { - var event = new VaultUpdatedEvent(); - event.setTimestamp(Instant.now()); - event.setUpdatedBy(updatedBy); - event.setVaultId(vaultId); - event.setVaultName(vaultName); - event.setVaultDescription(vaultDescription); - event.setVaultArchived(vaultArchived); - persist(event); - } - -} 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 b70916e65..6cb24d8ad 100644 --- a/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java +++ b/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java @@ -9,9 +9,9 @@ import jakarta.ws.rs.container.ResourceInfo; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.ext.Provider; -import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; +import org.cryptomator.hub.entities.EffectiveVaultAccess; +import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.VaultAccess; -import org.cryptomator.hub.entities.VaultRepository; import org.eclipse.microprofile.jwt.JsonWebToken; import java.util.Arrays; @@ -31,9 +31,9 @@ public class VaultRoleFilter implements ContainerRequestFilter { JsonWebToken jwt; @Inject - EffectiveVaultAccessRepository effectiveVaultAccessRepo; + EffectiveVaultAccess.Repository effectiveVaultAccessRepo; @Inject - VaultRepository vaultRepo; + Vault.Repository vaultRepo; @Context ResourceInfo resourceInfo; 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 d32d7b6d0..d11bf367a 100644 --- a/backend/src/main/java/org/cryptomator/hub/license/LicenseHolder.java +++ b/backend/src/main/java/org/cryptomator/hub/license/LicenseHolder.java @@ -11,7 +11,6 @@ import jakarta.inject.Inject; import jakarta.transaction.Transactional; import org.cryptomator.hub.entities.Settings; -import org.cryptomator.hub.entities.SettingsRepository; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; @@ -23,7 +22,6 @@ import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.Objects; import java.util.Optional; @ApplicationScoped @@ -50,7 +48,7 @@ public class LicenseHolder implements Scheduled.SkipPredicate { @Inject RandomMinuteSleeper randomMinuteSleeper; @Inject - SettingsRepository settingsRepo; + Settings.Repository settingsRepo; private static final Logger LOG = Logger.getLogger(LicenseHolder.class); private DecodedJWT license; @@ -64,7 +62,7 @@ void init() { if (settings.getLicenseKey() != null && settings.getHubId() != null) { validateOrResetExistingLicense(settings); } else if (initialLicenseToken.isPresent() && initialId.isPresent()) { - validateAndApplyInitLicense(settings, initialLicenseToken.get(), initialId.get() ); + validateAndApplyInitLicense(settings, initialLicenseToken.get(), initialId.get()); } } diff --git a/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java b/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java index 40554b461..bd024fcce 100644 --- a/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java +++ b/backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java @@ -1,13 +1,10 @@ package org.cryptomator.hub; import org.cryptomator.hub.entities.Authority; -import org.cryptomator.hub.entities.AuthorityRepository; import org.cryptomator.hub.entities.Group; import org.cryptomator.hub.entities.User; -import org.cryptomator.hub.entities.UserRepository; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -29,8 +26,8 @@ class RemoteUserPullerTest { private final RemoteUserProvider remoteUserProvider = Mockito.mock(RemoteUserProvider.class); private final User user = Mockito.mock(User.class); - private final AuthorityRepository authorityRepo = Mockito.mock(AuthorityRepository.class); - private final UserRepository userRepo = Mockito.mock(UserRepository.class); + private final Authority.Repository authorityRepo = Mockito.mock(Authority.Repository.class); + private final User.Repository userRepo = Mockito.mock(User.Repository.class); private RemoteUserPuller remoteUserPuller; diff --git a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java index bd54b8e39..bf3f88fa9 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java +++ b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java @@ -11,7 +11,7 @@ import io.restassured.http.ContentType; import jakarta.inject.Inject; import jakarta.validation.Validator; -import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; +import org.cryptomator.hub.entities.EffectiveVaultAccess; import org.cryptomator.hub.rollback.DBRollback; import org.flywaydb.core.Flyway; import org.hamcrest.MatcherAssert; @@ -61,7 +61,7 @@ public class VaultResourceIT { @Inject AgroalDataSource dataSource; @Inject - EffectiveVaultAccessRepository effectiveVaultAccessRepo; + EffectiveVaultAccess.Repository effectiveVaultAccessRepo; @Inject Validator validator; @Inject diff --git a/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java b/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java index 978013001..e0c3ae90f 100644 --- a/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java +++ b/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java @@ -16,9 +16,9 @@ public class EntityIT { @Inject - AccessTokenRepository accessTokenRepo; + AccessToken.Repository accessTokenRepo; @Inject - UserRepository userRepo; + User.Repository userRepo; @Inject AgroalDataSource dataSource; 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 02bec32a2..b6a2368a8 100644 --- a/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java +++ b/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java @@ -7,10 +7,9 @@ import jakarta.ws.rs.container.ResourceInfo; import jakarta.ws.rs.core.MultivaluedHashMap; import jakarta.ws.rs.core.UriInfo; -import org.cryptomator.hub.entities.EffectiveVaultAccessRepository; +import org.cryptomator.hub.entities.EffectiveVaultAccess; import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.VaultAccess; -import org.cryptomator.hub.entities.VaultRepository; import org.eclipse.microprofile.jwt.JsonWebToken; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -30,8 +29,8 @@ public class VaultRoleFilterTest { private final UriInfo uriInfo = Mockito.mock(UriInfo.class); private final ContainerRequestContext context = Mockito.mock(ContainerRequestContext.class); private final JsonWebToken jwt = Mockito.mock(JsonWebToken.class); - private final EffectiveVaultAccessRepository effectiveVaultAccessRepo = Mockito.mock(EffectiveVaultAccessRepository.class); - private final VaultRepository vaultRepo = Mockito.mock(VaultRepository.class); + private final EffectiveVaultAccess.Repository effectiveVaultAccessRepo = Mockito.mock(EffectiveVaultAccess.Repository.class); + private final Vault.Repository vaultRepo = Mockito.mock(Vault.Repository.class); private final VaultRoleFilter filter = new VaultRoleFilter(); @BeforeEach 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 6b5bd13bc..2e2e7db6e 100644 --- a/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderTest.java +++ b/backend/src/test/java/org/cryptomator/hub/license/LicenseHolderTest.java @@ -4,7 +4,6 @@ import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import org.cryptomator.hub.entities.Settings; -import org.cryptomator.hub.entities.SettingsRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -32,7 +31,7 @@ public class LicenseHolderTest { - SettingsRepository settingsRepo = mock(SettingsRepository.class); + Settings.Repository settingsRepo = mock(Settings.Repository.class); RandomMinuteSleeper randomMinuteSleeper = mock(RandomMinuteSleeper.class); LicenseValidator validator = mock(LicenseValidator.class); @@ -344,7 +343,7 @@ class RequestLicenseRefresh { @Test public void testSucess() throws IOException, InterruptedException { URI refreshUrl = URI.create("https://localhost:3000"); - try( var httpClientMock = Mockito.mockStatic(HttpClient.class)) { + try (var httpClientMock = Mockito.mockStatic(HttpClient.class)) { var httpClient = mock(HttpClient.class); var httpBuilder = mock(HttpClient.Builder.class); when(httpBuilder.build()).thenReturn(httpClient); @@ -364,7 +363,7 @@ public void testSucess() throws IOException, InterruptedException { @Test public void test500Response() throws IOException, InterruptedException { URI refreshUrl = URI.create("https://localhost:3000"); - try( var httpClientMock = Mockito.mockStatic(HttpClient.class)) { + try (var httpClientMock = Mockito.mockStatic(HttpClient.class)) { var httpClient = mock(HttpClient.class); var httpBuilder = mock(HttpClient.Builder.class); when(httpBuilder.build()).thenReturn(httpClient); @@ -376,14 +375,14 @@ public void test500Response() throws IOException, InterruptedException { when(response.body()).thenReturn("newToken"); when(httpClient.send(argThat(request -> request.uri().equals(refreshUrl)), any())).thenReturn(response); - Assertions.assertThrows(LicenseHolder.LicenseRefreshFailedException.class,() -> licenseHolder.requestLicenseRefresh(refreshUrl, "token")); + Assertions.assertThrows(LicenseHolder.LicenseRefreshFailedException.class, () -> licenseHolder.requestLicenseRefresh(refreshUrl, "token")); } } @Test public void testEmtyBody() throws IOException, InterruptedException { URI refreshUrl = URI.create("https://localhost:3000"); - try( var httpClientMock = Mockito.mockStatic(HttpClient.class)) { + try (var httpClientMock = Mockito.mockStatic(HttpClient.class)) { var httpClient = mock(HttpClient.class); var httpBuilder = mock(HttpClient.Builder.class); when(httpBuilder.build()).thenReturn(httpClient); @@ -395,7 +394,7 @@ public void testEmtyBody() throws IOException, InterruptedException { when(response.body()).thenReturn(""); when(httpClient.send(argThat(request -> request.uri().equals(refreshUrl)), any())).thenReturn(response); - Assertions.assertThrows(LicenseHolder.LicenseRefreshFailedException.class,() -> licenseHolder.requestLicenseRefresh(refreshUrl, "token")); + Assertions.assertThrows(LicenseHolder.LicenseRefreshFailedException.class, () -> licenseHolder.requestLicenseRefresh(refreshUrl, "token")); } } } From dcf15877ad0139b8a82c716b1bb9f06e881bbbc5 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 9 Apr 2024 12:06:53 +0200 Subject: [PATCH 40/54] Update application.properties Remove sql dialect property (not necessary) --- backend/src/main/resources/application.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index c6078f01d..e2e825cd6 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -58,7 +58,6 @@ quarkus.datasource.db-kind=postgresql quarkus.datasource.jdbc.driver=org.postgresql.Driver quarkus.datasource.jdbc.transaction-requirement=off quarkus.datasource.jdbc.max-size=16 -quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQLDialect quarkus.hibernate-orm.database.globally-quoted-identifiers=true quarkus.flyway.migrate-at-start=true quarkus.flyway.locations=classpath:org/cryptomator/hub/flyway From 081349e42c5e0920a2df477b77d419b08200e849 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 9 Apr 2024 12:14:32 +0200 Subject: [PATCH 41/54] apply suggestions from code review Co-authored-by: Sebastian Stenzel --- .../src/main/java/org/cryptomator/hub/entities/Settings.java | 4 ++-- .../main/java/org/cryptomator/hub/license/LicenseHolder.java | 2 -- backend/src/main/resources/application.properties | 1 + 3 files changed, 3 insertions(+), 4 deletions(-) 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 0f9415ebd..2bcac119d 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Settings.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Settings.java @@ -13,7 +13,7 @@ @Table(name = "settings") public class Settings { - static final int SINGLETON_ID = 0; + private static final long SINGLETON_ID = 0L; @Id @Column(name = "id", nullable = false, updatable = false) @@ -77,7 +77,7 @@ public int hashCode() { public static class Repository implements PanacheRepository { public Settings get() { - return Objects.requireNonNull(findById((long) SINGLETON_ID), "Settings not initialized"); + return Objects.requireNonNull(findById(SINGLETON_ID), "Settings not initialized"); } } } 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 d11bf367a..a2c6dfe59 100644 --- a/backend/src/main/java/org/cryptomator/hub/license/LicenseHolder.java +++ b/backend/src/main/java/org/cryptomator/hub/license/LicenseHolder.java @@ -5,7 +5,6 @@ import com.auth0.jwt.interfaces.DecodedJWT; import io.quarkus.scheduler.Scheduled; import io.quarkus.scheduler.ScheduledExecution; -import io.smallrye.common.annotation.NonBlocking; import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -108,7 +107,6 @@ public void set(String token) throws JWTVerificationException { * Attempts to refresh the Hub licence every day between 01:00:00 and 02:00:00 AM UTC if claim refreshURL is present. */ @Scheduled(cron = "0 0 1 * * ?", timeZone = "UTC", concurrentExecution = Scheduled.ConcurrentExecution.SKIP, skipExecutionIf = LicenseHolder.class) - @NonBlocking void refreshLicense() throws InterruptedException { randomMinuteSleeper.sleep(); // add random sleep between [0,59]min to reduce infrastructure load var refreshUrlClaim = get().getClaim("refreshUrl"); diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index e2e825cd6..c6078f01d 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -58,6 +58,7 @@ quarkus.datasource.db-kind=postgresql quarkus.datasource.jdbc.driver=org.postgresql.Driver quarkus.datasource.jdbc.transaction-requirement=off quarkus.datasource.jdbc.max-size=16 +quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQLDialect quarkus.hibernate-orm.database.globally-quoted-identifiers=true quarkus.flyway.migrate-at-start=true quarkus.flyway.locations=classpath:org/cryptomator/hub/flyway From 4f023e0ce15588f06fead5512294c41af2cccf77 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 9 Apr 2024 12:21:19 +0200 Subject: [PATCH 42/54] extend db rollback annotations: * improve naming of db rollback after test execution * add annotation for before test execution --- .../org/cryptomator/hub/RollbackTestIT.java | 4 +- .../cryptomator/hub/api/VaultResourceIT.java | 16 ++--- .../{DBRollback.java => DBRollbackAfter.java} | 2 +- ...ion.java => DBRollbackAfterExtension.java} | 4 +- .../hub/rollback/DBRollbackBefore.java | 11 ++++ .../rollback/DBRollbackBeforeExtension.java | 61 +++++++++++++++++++ ...callback.QuarkusTestAfterConstructCallback | 3 +- ...back.QuarkusTestAfterTestExecutionCallback | 2 +- ...ack.QuarkusTestBeforeTestExecutionCallback | 1 + 9 files changed, 87 insertions(+), 17 deletions(-) rename backend/src/test/java/org/cryptomator/hub/rollback/{DBRollback.java => DBRollbackAfter.java} (88%) rename backend/src/test/java/org/cryptomator/hub/rollback/{DBRollbackExtension.java => DBRollbackAfterExtension.java} (93%) create mode 100644 backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackBefore.java create mode 100644 backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackBeforeExtension.java create mode 100644 backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestBeforeTestExecutionCallback diff --git a/backend/src/test/java/org/cryptomator/hub/RollbackTestIT.java b/backend/src/test/java/org/cryptomator/hub/RollbackTestIT.java index 43c7bfa98..98938d4a6 100644 --- a/backend/src/test/java/org/cryptomator/hub/RollbackTestIT.java +++ b/backend/src/test/java/org/cryptomator/hub/RollbackTestIT.java @@ -3,7 +3,7 @@ import io.agroal.api.AgroalDataSource; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; -import org.cryptomator.hub.rollback.DBRollback; +import org.cryptomator.hub.rollback.DBRollbackAfter; import org.flywaydb.core.Flyway; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Nested; @@ -23,7 +23,7 @@ public class RollbackTestIT { class WithFlywayCleanup { @Test - @DBRollback + @DBRollbackAfter public void test1() throws SQLException { try (var c = dataSource.getConnection(); var s = c.createStatement()) { s.execute(""" diff --git a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java index bf3f88fa9..7e4f331e8 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java +++ b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java @@ -12,7 +12,8 @@ import jakarta.inject.Inject; import jakarta.validation.Validator; import org.cryptomator.hub.entities.EffectiveVaultAccess; -import org.cryptomator.hub.rollback.DBRollback; +import org.cryptomator.hub.rollback.DBRollbackAfter; +import org.cryptomator.hub.rollback.DBRollbackBefore; import org.flywaydb.core.Flyway; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; @@ -229,14 +230,9 @@ public void testUnlockArchived() { }) public class AsAuthorizedUser2 { - @BeforeEach - public void resetDB() { - flyway.clean(); - flyway.migrate(); - } - @Test @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/access-token returns 449, because user2 is not initialized") + @DBRollbackBefore public void testUnlock() { when().get("/vaults/{vaultId}/access-token", "7E57C0DE-0000-4000-8000-000100001111") .then().statusCode(449); @@ -296,7 +292,7 @@ public void testCreateVault3() { @Test @Order(2) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100003333 returns 200, updating only name, description and archive flag") - @DBRollback + @DBRollbackAfter public void testUpdateVault() { var uuid = UUID.fromString("7E57C0DE-0000-4000-8000-000100003333"); var vaultDto = new VaultResource.VaultDto(uuid, "VaultUpdated", "Vault updated.", true, Instant.parse("2222-11-11T11:11:11Z"), "doNotUpdate", 27, "doNotUpdate", "doNotUpdate", "doNotUpdate"); @@ -536,7 +532,7 @@ public void testRevokeAccess() { // previously added in testGrantAccess() @Test @Order(14) @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/members does not contain user2") - @DBRollback + @DBRollbackAfter public void getMembersOfVault2c() { given().when().get("/vaults/{vaultId}/members", "7E57C0DE-0000-4000-8000-000100002222") .then().statusCode(200) @@ -637,7 +633,7 @@ public void removeGroup2() { @Test @Order(8) @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/members does not contain group2") - @DBRollback + @DBRollbackAfter public void getMembersOfVault1b() { given().when().get("/vaults/{vaultId}/members", "7E57C0DE-0000-4000-8000-000100001111") .then().statusCode(200) diff --git a/backend/src/test/java/org/cryptomator/hub/rollback/DBRollback.java b/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackAfter.java similarity index 88% rename from backend/src/test/java/org/cryptomator/hub/rollback/DBRollback.java rename to backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackAfter.java index 2d1416400..270b421fe 100644 --- a/backend/src/test/java/org/cryptomator/hub/rollback/DBRollback.java +++ b/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackAfter.java @@ -7,5 +7,5 @@ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) -public @interface DBRollback { +public @interface DBRollbackAfter { } \ No newline at end of file diff --git a/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackExtension.java b/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackAfterExtension.java similarity index 93% rename from backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackExtension.java rename to backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackAfterExtension.java index 89bbdda2b..671637f51 100644 --- a/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackExtension.java +++ b/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackAfterExtension.java @@ -11,13 +11,13 @@ import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; -public class DBRollbackExtension implements QuarkusTestAfterConstructCallback, QuarkusTestAfterTestExecutionCallback { +public class DBRollbackAfterExtension implements QuarkusTestAfterConstructCallback, QuarkusTestAfterTestExecutionCallback { static final AtomicReference INSTANCE = new AtomicReference<>(null); @Override public void afterTestExecution(QuarkusTestMethodContext context) { - var isAnnotationPresent = context.getTestMethod().getAnnotation(DBRollback.class) != null; + var isAnnotationPresent = context.getTestMethod().getAnnotation(DBRollbackAfter.class) != null; if(isAnnotationPresent) { var flyway = INSTANCE.get(); if(flyway == null) { diff --git a/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackBefore.java b/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackBefore.java new file mode 100644 index 000000000..6e908e2aa --- /dev/null +++ b/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackBefore.java @@ -0,0 +1,11 @@ +package org.cryptomator.hub.rollback; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DBRollbackBefore { +} \ No newline at end of file diff --git a/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackBeforeExtension.java b/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackBeforeExtension.java new file mode 100644 index 000000000..47d8c3885 --- /dev/null +++ b/backend/src/test/java/org/cryptomator/hub/rollback/DBRollbackBeforeExtension.java @@ -0,0 +1,61 @@ +package org.cryptomator.hub.rollback; + +import io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback; +import io.quarkus.test.junit.callback.QuarkusTestAfterTestExecutionCallback; +import io.quarkus.test.junit.callback.QuarkusTestBeforeTestExecutionCallback; +import io.quarkus.test.junit.callback.QuarkusTestMethodContext; +import org.flywaydb.core.Flyway; +import org.junit.jupiter.api.Nested; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; + +public class DBRollbackBeforeExtension implements QuarkusTestAfterConstructCallback, QuarkusTestBeforeTestExecutionCallback { + + static final AtomicReference INSTANCE = new AtomicReference<>(null); + + @Override + public void beforeTestExecution(QuarkusTestMethodContext context) { + var isAnnotationPresent = context.getTestMethod().getAnnotation(DBRollbackAfter.class) != null; + if(isAnnotationPresent) { + var flyway = INSTANCE.get(); + if(flyway == null) { + throw new IllegalStateException("Flyway instance was not set. Please ensure that test class (or enclosing class) have a public non-null, Flyway field."); + } + + flyway.clean(); + flyway.migrate(); + } + } + + @Override + public void afterConstruct(Object testInstance) { + try { + var topLevelTestInstance = getTopLevelTestInstance(testInstance); + INSTANCE.set(getFlywayObject(topLevelTestInstance)); + } catch (NoSuchFieldException | IllegalAccessException e) { + //no-op + } + } + + private Object getTopLevelTestInstance(Object testInstance) throws NoSuchFieldException, IllegalAccessException { + var testClazz = testInstance.getClass(); + var hasEnclosingClass = testClazz.getEnclosingClass() != null; + var isNotStatic = !Modifier.isStatic(testClazz.getModifiers()); + var isNested = testClazz.getAnnotation(Nested.class) != null; + if(hasEnclosingClass && isNotStatic && isNested) { + Field field = testClazz.getDeclaredField("this$0"); + field.setAccessible(true); + return getTopLevelTestInstance(field.get(testInstance)); + } else { + return testInstance; + } + } + + private Flyway getFlywayObject(Object obj) throws NoSuchFieldException, IllegalAccessException { + var flywayField = Arrays.stream(obj.getClass().getFields()).filter(field -> field.getType().equals(Flyway.class)).findFirst().orElseThrow(NoSuchFieldException::new); + return (Flyway) flywayField.get(obj); + } +} diff --git a/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback b/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback index 252d8cb65..d3eb60717 100644 --- a/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback +++ b/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback @@ -1 +1,2 @@ -org.cryptomator.hub.rollback.DBRollbackExtension \ No newline at end of file +org.cryptomator.hub.rollback.DBRollbackAfterExtension +org.cryptomator.hub.rollback.DBRollbackBeforeExtension \ No newline at end of file diff --git a/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterTestExecutionCallback b/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterTestExecutionCallback index 252d8cb65..1286a2cb8 100644 --- a/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterTestExecutionCallback +++ b/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterTestExecutionCallback @@ -1 +1 @@ -org.cryptomator.hub.rollback.DBRollbackExtension \ No newline at end of file +org.cryptomator.hub.rollback.DBRollbackAfterExtension \ No newline at end of file diff --git a/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestBeforeTestExecutionCallback b/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestBeforeTestExecutionCallback new file mode 100644 index 000000000..7d3b2be2e --- /dev/null +++ b/backend/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestBeforeTestExecutionCallback @@ -0,0 +1 @@ +org.cryptomator.hub.rollback.DBRollbackBeforeExtension \ No newline at end of file From 2e52338fe40c7087a2dd3df1570e30166220aee0 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 9 Apr 2024 13:06:44 +0200 Subject: [PATCH 43/54] remove db rollback for last test in nested class --- .../src/test/java/org/cryptomator/hub/api/VaultResourceIT.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java index 7e4f331e8..eba8bb42f 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java +++ b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java @@ -292,7 +292,6 @@ public void testCreateVault3() { @Test @Order(2) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100003333 returns 200, updating only name, description and archive flag") - @DBRollbackAfter public void testUpdateVault() { var uuid = UUID.fromString("7E57C0DE-0000-4000-8000-000100003333"); var vaultDto = new VaultResource.VaultDto(uuid, "VaultUpdated", "Vault updated.", true, Instant.parse("2222-11-11T11:11:11Z"), "doNotUpdate", 27, "doNotUpdate", "doNotUpdate", "doNotUpdate"); From 7b77d30000205cc05d1db69c20adad4ea7e14228 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 9 Apr 2024 18:25:42 +0200 Subject: [PATCH 44/54] cleanup --- .../src/test/java/org/cryptomator/hub/api/VaultResourceIT.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java index eba8bb42f..a76afc4a2 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java +++ b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java @@ -19,7 +19,6 @@ import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Nested; From 75514a2e3975e24fbe9fd703563affb876cd9e4f Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 9 Apr 2024 18:28:23 +0200 Subject: [PATCH 45/54] refactor event repositories * new eventLogger class * remove specific event repositories --- .../cryptomator/hub/api/DeviceResource.java | 10 +- .../cryptomator/hub/api/UsersResource.java | 5 +- .../cryptomator/hub/api/VaultResource.java | 56 +++------ .../events/DeviceRegisteredEvent.java | 17 --- .../entities/events/DeviceRemovedEvent.java | 16 --- .../hub/entities/events/EventLogger.java | 111 ++++++++++++++++++ .../events/VaultAccessGrantedEvent.java | 16 --- .../entities/events/VaultCreatedEvent.java | 16 --- .../events/VaultKeyRetrievedEvent.java | 15 --- .../events/VaultMemberAddedEvent.java | 16 --- .../events/VaultMemberRemovedEvent.java | 16 --- .../events/VaultMemberUpdatedEvent.java | 17 --- .../events/VaultOwnershipClaimedEvent.java | 14 --- .../entities/events/VaultUpdatedEvent.java | 14 --- 14 files changed, 136 insertions(+), 203 deletions(-) create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/events/EventLogger.java 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 bf5f36028..571157d50 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java @@ -24,8 +24,8 @@ import org.cryptomator.hub.entities.LegacyAccessToken; import org.cryptomator.hub.entities.LegacyDevice; import org.cryptomator.hub.entities.User; -import org.cryptomator.hub.entities.events.DeviceRegisteredEvent; import org.cryptomator.hub.entities.events.DeviceRemovedEvent; +import org.cryptomator.hub.entities.events.EventLogger; import org.cryptomator.hub.validation.NoHtmlOrScriptChars; import org.cryptomator.hub.validation.OnlyBase64Chars; import org.cryptomator.hub.validation.ValidId; @@ -51,9 +51,7 @@ public class DeviceResource { private static final Logger LOG = Logger.getLogger(DeviceResource.class); @Inject - DeviceRegisteredEvent.Repository deviceRegisteredEventRepo; - @Inject - DeviceRemovedEvent.Repository deviceRemovedEventRepo; + EventLogger eventLogger; @Inject User.Repository userRepo; @Inject @@ -108,7 +106,7 @@ public Response createOrUpdate(@Valid @NotNull DeviceDto dto, @PathParam("device try { deviceRepo.persistAndFlush(device); - deviceRegisteredEventRepo.log(jwt.getSubject(), deviceId, device.getName(), device.getType()); + eventLogger.logDeviceRegisted(jwt.getSubject(), deviceId, device.getName(), device.getType()); return Response.created(URI.create(".")).build(); } catch (ConstraintViolationException e) { throw new ClientErrorException(Response.Status.CONFLICT, e); @@ -164,7 +162,7 @@ public Response remove(@PathParam("deviceId") @ValidId String deviceId) { var maybeDevice = deviceRepo.findByIdOptional(deviceId); if (maybeDevice.isPresent() && currentUser.equals(maybeDevice.get().getOwner())) { deviceRepo.delete(maybeDevice.get()); - deviceRemovedEventRepo.log(jwt.getSubject(), deviceId); + eventLogger.logDeviceRemoved(jwt.getSubject(), deviceId); return Response.status(Response.Status.NO_CONTENT).build(); } else { return Response.status(Response.Status.NOT_FOUND).build(); 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 32639ba76..f754a1320 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java @@ -19,6 +19,7 @@ import org.cryptomator.hub.entities.Device; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.Vault; +import org.cryptomator.hub.entities.events.EventLogger; import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent; import org.eclipse.microprofile.jwt.JsonWebToken; import org.eclipse.microprofile.openapi.annotations.Operation; @@ -41,7 +42,7 @@ public class UsersResource { @Inject AccessToken.Repository accessTokenRepo; @Inject - VaultAccessGrantedEvent.Repository vaultAccessGrantedEventRepo; + EventLogger eventLogger; @Inject User.Repository userRepo; @Inject @@ -100,7 +101,7 @@ public Response updateMyAccessTokens(@NotNull Map tokens) { } token.setVaultKey(entry.getValue()); accessTokenRepo.persist(token); - vaultAccessGrantedEventRepo.log(user.getId(), vault.getId(), user.getId()); + eventLogger.logVaultAccessGranted(user.getId(), vault.getId(), user.getId()); } return Response.ok().build(); } 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 08cb98a5f..9a26905fc 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -38,14 +38,8 @@ import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.VaultAccess; -import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent; -import org.cryptomator.hub.entities.events.VaultCreatedEvent; +import org.cryptomator.hub.entities.events.EventLogger; import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; -import org.cryptomator.hub.entities.events.VaultMemberAddedEvent; -import org.cryptomator.hub.entities.events.VaultMemberRemovedEvent; -import org.cryptomator.hub.entities.events.VaultMemberUpdatedEvent; -import org.cryptomator.hub.entities.events.VaultOwnershipClaimedEvent; -import org.cryptomator.hub.entities.events.VaultUpdatedEvent; import org.cryptomator.hub.filters.ActiveLicense; import org.cryptomator.hub.filters.VaultRole; import org.cryptomator.hub.license.LicenseHolder; @@ -72,23 +66,10 @@ public class VaultResource { @Inject - AccessToken.Repository accessTokenRepo; - @Inject - VaultAccessGrantedEvent.Repository vaultAccessGrantedEventRepo; - @Inject - VaultCreatedEvent.Repository vaultCreatedEventRepo; - @Inject - VaultKeyRetrievedEvent.Repository vaultKeyRetrievedEventRepo; - @Inject - VaultMemberAddedEvent.Repository vaultMemberAddedEventRepo; - @Inject - VaultMemberRemovedEvent.Repository vaultMemberRemovedEventRepo; - @Inject - VaultMemberUpdatedEvent.Repository vaultMemberUpdatedEventRepo; - @Inject - VaultOwnershipClaimedEvent.Repository vaultOwnershipClaimedEventRepo; + EventLogger eventLogger; + @Inject - VaultUpdatedEvent.Repository vaultUpdatedEventRepo; + AccessToken.Repository accessTokenRepo; @Inject Group.Repository groupRepo; @Inject @@ -229,7 +210,7 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role var access = existingAccess.get(); access.setRole(role); vaultAccessRepo.persist(access); - vaultMemberUpdatedEventRepo.log(jwt.getSubject(), vault.getId(), authority.getId(), role); + eventLogger.logVaultMemberUpdated(jwt.getSubject(), vault.getId(), authority.getId(), role); return Response.ok().build(); } else { var access = new VaultAccess(); @@ -237,7 +218,7 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role access.setAuthority(authority); access.setRole(role); vaultAccessRepo.persist(access); - vaultMemberAddedEventRepo.log(jwt.getSubject(), vault.getId(), authority.getId(), role); + eventLogger.logVaultMemberAdded(jwt.getSubject(), vault.getId(), authority.getId(), role); return Response.created(URI.create(".")).build(); } } @@ -253,7 +234,7 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role @APIResponse(responseCode = "403", description = "not a vault owner") public Response removeAuthority(@PathParam("vaultId") UUID vaultId, @PathParam("authorityId") @ValidId String authorityId) { if (vaultAccessRepo.deleteById(new VaultAccess.Id(vaultId, authorityId))) { - vaultMemberRemovedEventRepo.log(jwt.getSubject(), vaultId, authorityId); + eventLogger.logVaultMemberRemoved(jwt.getSubject(), vaultId, authorityId); return Response.status(Response.Status.NO_CONTENT).build(); } else { throw new NotFoundException(); @@ -298,12 +279,12 @@ public Response legacyUnlock(@PathParam("vaultId") UUID vaultId, @PathParam("dev var access = legacyAccessTokenRepo.unlock(vaultId, deviceId, jwt.getSubject()); if (access != null) { - vaultKeyRetrievedEventRepo.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); + eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); var subscriptionStateHeaderName = "Hub-Subscription-State"; var subscriptionStateHeaderValue = license.isSet() ? "ACTIVE" : "INACTIVE"; // license expiration is not checked here, because it is checked in the ActiveLicense filter return Response.ok(access.getJwe()).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); } else { - vaultKeyRetrievedEventRepo.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); + eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); throw new ForbiddenException("Access to this device not granted."); } } @@ -340,14 +321,14 @@ public Response unlock(@PathParam("vaultId") UUID vaultId, @QueryParam("evenIfAr var access = accessTokenRepo.unlock(vaultId, jwt.getSubject()); if (access != null) { - vaultKeyRetrievedEventRepo.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); + eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); var subscriptionStateHeaderName = "Hub-Subscription-State"; var subscriptionStateHeaderValue = license.isSet() ? "ACTIVE" : "INACTIVE"; // license expiration is not checked here, because it is checked in the ActiveLicense filter return Response.ok(access.getVaultKey()).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); } else if (vaultRepo.findById(vaultId) == null) { throw new NotFoundException("No such vault."); } else { - vaultKeyRetrievedEventRepo.log(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); + eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); throw new ForbiddenException("Access to this vault not granted."); } } @@ -388,7 +369,7 @@ public Response grantAccess(@PathParam("vaultId") UUID vaultId, @NotEmpty Map { - - public void log(String registeredBy, String deviceId, String deviceName, Device.Type deviceType) { - var event = new DeviceRegisteredEvent(); - event.setTimestamp(Instant.now()); - event.setRegisteredBy(registeredBy); - event.setDeviceId(deviceId); - event.setDeviceName(deviceName); - event.setDeviceType(deviceType); - persist(event); - } - } } 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 5dc2b73e2..597236f0b 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 @@ -1,13 +1,10 @@ package org.cryptomator.hub.entities.events; -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; -import java.time.Instant; import java.util.Objects; @Entity @@ -54,17 +51,4 @@ public int hashCode() { return Objects.hash(id, removedBy, deviceId); } - - @ApplicationScoped - public static class Repository implements PanacheRepository { - - public void log(String removedBy, String deviceId) { - var event = new DeviceRemovedEvent(); - event.setTimestamp(Instant.now()); - event.setRemovedBy(removedBy); - event.setDeviceId(deviceId); - persist(event); - } - - } } 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 new file mode 100644 index 000000000..d2839e109 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/EventLogger.java @@ -0,0 +1,111 @@ +package org.cryptomator.hub.entities.events; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.cryptomator.hub.entities.Device; +import org.cryptomator.hub.entities.VaultAccess; + +import java.time.Instant; +import java.util.UUID; + +@ApplicationScoped +public class EventLogger { + + @Inject + AuditEvent.Repository auditEventRepository; + + public void logVaultCreated(String createdBy, UUID vaultId, String vaultName, String vaultDescription) { + var event = new VaultCreatedEvent(); + event.setTimestamp(Instant.now()); + event.setCreatedBy(createdBy); + event.setVaultId(vaultId); + event.setVaultName(vaultName); + event.setVaultDescription(vaultDescription); + auditEventRepository.persist(event); + } + + public void logVaultUpdated(String updatedBy, UUID vaultId, String vaultName, String vaultDescription, boolean vaultArchived) { + var event = new VaultUpdatedEvent(); + event.setTimestamp(Instant.now()); + event.setUpdatedBy(updatedBy); + event.setVaultId(vaultId); + event.setVaultName(vaultName); + event.setVaultDescription(vaultDescription); + event.setVaultArchived(vaultArchived); + auditEventRepository.persist(event); + } + + public void logDeviceRegisted(String registeredBy, String deviceId, String deviceName, Device.Type deviceType) { + var event = new DeviceRegisteredEvent(); + event.setTimestamp(Instant.now()); + event.setRegisteredBy(registeredBy); + event.setDeviceId(deviceId); + event.setDeviceName(deviceName); + event.setDeviceType(deviceType); + auditEventRepository.persist(event); + } + + public void logDeviceRemoved(String removedBy, String deviceId) { + var event = new DeviceRemovedEvent(); + event.setTimestamp(Instant.now()); + event.setRemovedBy(removedBy); + event.setDeviceId(deviceId); + auditEventRepository.persist(event); + } + + public void logVaultAccessGranted(String grantedBy, UUID vaultId, String authorityId) { + var event = new VaultAccessGrantedEvent(); + event.setTimestamp(Instant.now()); + event.setGrantedBy(grantedBy); + event.setVaultId(vaultId); + event.setAuthorityId(authorityId); + auditEventRepository.persist(event); + } + + public void logVaultKeyRetrieved(String retrievedBy, UUID vaultId, VaultKeyRetrievedEvent.Result result) { + var event = new VaultKeyRetrievedEvent(); + event.setTimestamp(Instant.now()); + event.setRetrievedBy(retrievedBy); + event.setVaultId(vaultId); + event.setResult(result); + auditEventRepository.persist(event); + } + + public void logVaultMemberAdded(String addedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { + var event = new VaultMemberAddedEvent(); + event.setTimestamp(Instant.now()); + event.setAddedBy(addedBy); + event.setVaultId(vaultId); + event.setAuthorityId(authorityId); + event.setRole(role); + auditEventRepository.persist(event); + } + + public void logVaultMemberRemoved(String removedBy, UUID vaultId, String authorityId) { + var event = new VaultMemberRemovedEvent(); + event.setTimestamp(Instant.now()); + event.setRemovedBy(removedBy); + event.setVaultId(vaultId); + event.setAuthorityId(authorityId); + auditEventRepository.persist(event); + } + + public void logVaultMemberUpdated(String updatedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { + var event = new VaultMemberUpdatedEvent(); + event.setTimestamp(Instant.now()); + event.setUpdatedBy(updatedBy); + event.setVaultId(vaultId); + event.setAuthorityId(authorityId); + event.setRole(role); + auditEventRepository.persist(event); + } + + //legacy + public void logVaultOwnershipClaimed(String claimedBy, UUID vaultId) { + var event = new VaultOwnershipClaimedEvent(); + event.setTimestamp(Instant.now()); + event.setClaimedBy(claimedBy); + event.setVaultId(vaultId); + auditEventRepository.persist(event); + } +} 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 aa4bbba67..3b809d822 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 @@ -1,13 +1,10 @@ package org.cryptomator.hub.entities.events; -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; -import java.time.Instant; import java.util.Objects; import java.util.UUID; @@ -67,17 +64,4 @@ public int hashCode() { return Objects.hash(id, grantedBy, vaultId, authorityId); } - @ApplicationScoped - public static class Repository implements PanacheRepository { - - public void log(String grantedBy, UUID vaultId, String authorityId) { - var event = new VaultAccessGrantedEvent(); - event.setTimestamp(Instant.now()); - event.setGrantedBy(grantedBy); - event.setVaultId(vaultId); - event.setAuthorityId(authorityId); - persist(event); - } - - } } 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 8b472178c..a85cf844d 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 @@ -1,13 +1,10 @@ package org.cryptomator.hub.entities.events; -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; -import java.time.Instant; import java.util.Objects; import java.util.UUID; @@ -79,17 +76,4 @@ public int hashCode() { return Objects.hash(id, createdBy, vaultId, vaultName, vaultDescription); } - @ApplicationScoped - public static class Repository implements PanacheRepository { - - public void log(String createdBy, UUID vaultId, String vaultName, String vaultDescription) { - var event = new VaultCreatedEvent(); - event.setTimestamp(Instant.now()); - event.setCreatedBy(createdBy); - event.setVaultId(vaultId); - event.setVaultName(vaultName); - event.setVaultDescription(vaultDescription); - persist(event); - } - } } 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 dbef6cfe3..9fefac00b 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 @@ -1,7 +1,5 @@ package org.cryptomator.hub.entities.events; -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -9,7 +7,6 @@ import jakarta.persistence.Enumerated; import jakarta.persistence.Table; -import java.time.Instant; import java.util.Objects; import java.util.UUID; @@ -75,16 +72,4 @@ public enum Result { UNAUTHORIZED } - @ApplicationScoped - public static class Repository implements PanacheRepository { - - public void log(String retrievedBy, UUID vaultId, VaultKeyRetrievedEvent.Result result) { - var event = new VaultKeyRetrievedEvent(); - event.setTimestamp(Instant.now()); - event.setRetrievedBy(retrievedBy); - event.setVaultId(vaultId); - event.setResult(result); - persist(event); - } - } } 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 59ccf21c1..70a61477b 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 @@ -1,7 +1,5 @@ package org.cryptomator.hub.entities.events; -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -10,7 +8,6 @@ import jakarta.persistence.Table; import org.cryptomator.hub.entities.VaultAccess; -import java.time.Instant; import java.util.Objects; import java.util.UUID; @@ -83,17 +80,4 @@ public int hashCode() { return Objects.hash(id, addedBy, vaultId, authorityId, role); } - @ApplicationScoped - public static class Repository implements PanacheRepository { - - public void log(String addedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { - var event = new VaultMemberAddedEvent(); - event.setTimestamp(Instant.now()); - event.setAddedBy(addedBy); - event.setVaultId(vaultId); - event.setAuthorityId(authorityId); - event.setRole(role); - persist(event); - } - } } 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 cd4647218..48e07909f 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 @@ -1,13 +1,10 @@ package org.cryptomator.hub.entities.events; -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; -import java.time.Instant; import java.util.Objects; import java.util.UUID; @@ -67,17 +64,4 @@ public int hashCode() { return Objects.hash(id, removedBy, vaultId, authorityId); } - @ApplicationScoped - public static class Repository implements PanacheRepository { - - public void log(String removedBy, UUID vaultId, String authorityId) { - var event = new VaultMemberRemovedEvent(); - event.setTimestamp(Instant.now()); - event.setRemovedBy(removedBy); - event.setVaultId(vaultId); - event.setAuthorityId(authorityId); - persist(event); - } - - } } 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 06f62d900..4092d5c68 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 @@ -1,8 +1,5 @@ package org.cryptomator.hub.entities.events; -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; -import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -11,7 +8,6 @@ import jakarta.persistence.Table; import org.cryptomator.hub.entities.VaultAccess; -import java.time.Instant; import java.util.Objects; import java.util.UUID; @@ -84,17 +80,4 @@ public int hashCode() { return Objects.hash(id, updatedBy, vaultId, authorityId, role); } - @ApplicationScoped - public static class Repository implements PanacheRepository { - - public void log(String updatedBy, UUID vaultId, String authorityId, VaultAccess.Role role) { - var event = new VaultMemberUpdatedEvent(); - event.setTimestamp(Instant.now()); - event.setUpdatedBy(updatedBy); - event.setVaultId(vaultId); - event.setAuthorityId(authorityId); - event.setRole(role); - persist(event); - } - } } 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 aad508743..3d5e34a8b 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 @@ -1,13 +1,10 @@ package org.cryptomator.hub.entities.events; -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; -import java.time.Instant; import java.util.Objects; import java.util.UUID; @@ -55,15 +52,4 @@ public int hashCode() { return Objects.hash(id, claimedBy, vaultId); } - @ApplicationScoped - public static class Repository implements PanacheRepository { - - public void log(String claimedBy, UUID vaultId) { - var event = new VaultOwnershipClaimedEvent(); - event.setTimestamp(Instant.now()); - event.setClaimedBy(claimedBy); - event.setVaultId(vaultId); - persist(event); - } - } } 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 ab983ff5b..e961a6ed1 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 @@ -91,18 +91,4 @@ public int hashCode() { return Objects.hash(id, updatedBy, vaultId, vaultName, vaultDescription, vaultArchived); } - @ApplicationScoped - public static class Repository implements PanacheRepository { - - public void log(String updatedBy, UUID vaultId, String vaultName, String vaultDescription, boolean vaultArchived) { - var event = new VaultUpdatedEvent(); - event.setTimestamp(Instant.now()); - event.setUpdatedBy(updatedBy); - event.setVaultId(vaultId); - event.setVaultName(vaultName); - event.setVaultDescription(vaultDescription); - event.setVaultArchived(vaultArchived); - persist(event); - } - } } From da069504dd3c1c2d7e51b6bb0c58194a44d400df Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 10 Apr 2024 10:35:01 +0200 Subject: [PATCH 46/54] privatize entity fields --- .../cryptomator/hub/entities/AccessToken.java | 12 ++++---- .../cryptomator/hub/entities/Authority.java | 4 +-- .../org/cryptomator/hub/entities/Device.java | 16 +++++----- .../entities/EffectiveGroupMembership.java | 8 ++--- .../hub/entities/EffectiveVaultAccess.java | 8 ++--- .../org/cryptomator/hub/entities/Group.java | 2 +- .../hub/entities/LegacyAccessToken.java | 8 ++--- .../hub/entities/LegacyDevice.java | 4 +-- .../cryptomator/hub/entities/Settings.java | 6 ++-- .../org/cryptomator/hub/entities/User.java | 12 ++++---- .../org/cryptomator/hub/entities/Vault.java | 30 +++++++++---------- .../cryptomator/hub/entities/VaultAccess.java | 8 ++--- .../hub/entities/events/AuditEvent.java | 6 ++-- .../events/DeviceRegisteredEvent.java | 10 +++---- .../entities/events/DeviceRemovedEvent.java | 6 ++-- .../events/VaultAccessGrantedEvent.java | 8 ++--- .../entities/events/VaultCreatedEvent.java | 10 +++---- .../events/VaultKeyRetrievedEvent.java | 8 ++--- .../events/VaultMemberAddedEvent.java | 10 +++---- .../events/VaultMemberRemovedEvent.java | 8 ++--- .../events/VaultMemberUpdatedEvent.java | 10 +++---- .../events/VaultOwnershipClaimedEvent.java | 6 ++-- .../entities/events/VaultUpdatedEvent.java | 15 ++++------ .../cryptomator/hub/entities/EntityIT.java | 8 ++--- 24 files changed, 110 insertions(+), 113 deletions(-) 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 bbb5c32fe..0711c8e32 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java @@ -35,20 +35,20 @@ public class AccessToken { @EmbeddedId - AccessId id = new AccessId(); + private AccessId id = new AccessId(); @ManyToOne(optional = false, cascade = {CascadeType.REMOVE}) @MapsId("userId") @JoinColumn(name = "user_id") - User user; + private User user; @ManyToOne(optional = false, cascade = {CascadeType.REMOVE}) @MapsId("vaultId") @JoinColumn(name = "vault_id") - Vault vault; + private Vault vault; @Column(name = "vault_masterkey", nullable = false) - String vaultKey; + private String vaultKey; @Override public boolean equals(Object o) { @@ -102,8 +102,8 @@ public int hashCode() { public String toString() { return "Access{" + "id=" + id + - ", user=" + user.id + - ", vault=" + vault.id + + ", user=" + user.getId() + + ", vault=" + vault.getId() + ", vaultKey='" + vaultKey + '\'' + '}'; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/Authority.java b/backend/src/main/java/org/cryptomator/hub/entities/Authority.java index c96cf659f..60e114abc 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Authority.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Authority.java @@ -36,10 +36,10 @@ public class Authority { @Id @Column(name = "id", nullable = false) - String id; + private String id; @Column(name = "name", nullable = false) - String name; + private String name; public String getId() { return id; 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 6d16963f2..3d64a359e 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Device.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Device.java @@ -40,27 +40,27 @@ public enum Type { @Id @Column(name = "id", nullable = false) - String id; + private String id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "owner_id", updatable = false, nullable = false) - User owner; + private User owner; @Column(name = "name", nullable = false) - String name; + private String name; @Column(name = "type", nullable = false) @Enumerated(EnumType.STRING) - Type type; + private Type type; @Column(name = "publickey", nullable = false) - String publickey; + private String publickey; @Column(name = "user_privatekey", nullable = false) - String userPrivateKey; + private String userPrivateKey; @Column(name = "creation_time", nullable = false) - Instant creationTime; + private Instant creationTime; public String getId() { return id; @@ -122,7 +122,7 @@ public void setCreationTime(Instant creationTime) { public String toString() { return "Device{" + "id='" + id + '\'' + - ", owner=" + owner.id + + ", owner=" + owner.getId() + ", name='" + name + '\'' + ", type='" + type + '\'' + ", publickey='" + publickey + '\'' + 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 5ea179a19..26421c9eb 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveGroupMembership.java @@ -17,18 +17,18 @@ public class EffectiveGroupMembership { @EmbeddedId - Id id; + private Id id; - String path; + private String path; @Embeddable public static class Id implements Serializable { @Column(name = "group_id") - public String groupId; + private String groupId; @Column(name = "member_id") - public String memberId; + private String memberId; @Override public boolean equals(Object o) { 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 73d89034c..e55f4b5b1 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java @@ -65,7 +65,7 @@ SELECT count(DISTINCT u) public class EffectiveVaultAccess { @EmbeddedId - EffectiveVaultAccess.Id id; + private EffectiveVaultAccess.Id id; public Id getId() { return id; @@ -79,14 +79,14 @@ public void setId(Id id) { public static class Id implements Serializable { @Column(name = "vault_id") - UUID vaultId; + private UUID vaultId; @Column(name = "authority_id") - String authorityId; + private String authorityId; @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) - VaultAccess.Role role; + private VaultAccess.Role role; public UUID getVaultId() { return vaultId; 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 aeb108fb5..ae7b8be46 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Group.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Group.java @@ -22,7 +22,7 @@ public class Group extends Authority { joinColumns = @JoinColumn(name = "group_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "member_id", referencedColumnName = "id") ) - Set members = new HashSet<>(); + private Set members = new HashSet<>(); public Set getMembers() { return members; diff --git a/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java b/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java index 56b25aa92..d36c49e1b 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java @@ -35,10 +35,10 @@ public class LegacyAccessToken { @EmbeddedId - AccessId id = new AccessId(); + private AccessId id = new AccessId(); @Column(name = "jwe", nullable = false) - String jwe; + private String jwe; public AccessId getId() { return id; @@ -82,10 +82,10 @@ public String toString() { public static class AccessId implements Serializable { @Column(name = "device_id", nullable = false) - String deviceId; + private String deviceId; @Column(name = "vault_id", nullable = false) - UUID vaultId; + private UUID vaultId; public String getDeviceId() { return deviceId; 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 97d1064e0..268a25588 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/LegacyDevice.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/LegacyDevice.java @@ -14,10 +14,10 @@ public class LegacyDevice { @Id @Column(name = "id", nullable = false) - String id; + private String id; @Column(name = "owner_id", nullable = false) - String ownerId; + private String ownerId; public String getId() { return id; 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 2bcac119d..d521e548d 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Settings.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Settings.java @@ -17,13 +17,13 @@ public class Settings { @Id @Column(name = "id", nullable = false, updatable = false) - int id; + private int id; @Column(name = "hub_id", nullable = false) - String hubId; + private String hubId; @Column(name = "license_key") - String licenseKey; + private String licenseKey; public int getId() { return id; 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 78d87ce25..8bca0b0ca 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/User.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/User.java @@ -44,19 +44,19 @@ SELECT count( DISTINCT u) public class User extends Authority { @Column(name = "picture_url") - String pictureUrl; + private String pictureUrl; @Column(name = "email") - String email; + private String email; @Column(name = "publickey") - String publicKey; + private String publicKey; @Column(name = "privatekey") - String privateKey; + private String privateKey; @Column(name = "setupcode") - String setupCode; + private String setupCode; public String getPictureUrl() { return pictureUrl; @@ -135,7 +135,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(id, pictureUrl, email, publicKey, privateKey, setupCode); + return Objects.hash(super.getId(), pictureUrl, email, publicKey, privateKey, setupCode); } @ApplicationScoped 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 32ea9e5d2..ed1bdf077 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Vault.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Vault.java @@ -57,7 +57,7 @@ public class Vault { @Id @Column(name = "id", nullable = false) - UUID id; + private UUID id; @ManyToMany @Immutable @@ -65,7 +65,7 @@ public class Vault { joinColumns = @JoinColumn(name = "vault_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id") ) - Set directMembers = new HashSet<>(); + private Set directMembers = new HashSet<>(); @ManyToMany @Immutable @@ -73,37 +73,37 @@ public class Vault { joinColumns = @JoinColumn(name = "vault_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id") ) - Set effectiveMembers = new HashSet<>(); + private Set effectiveMembers = new HashSet<>(); @OneToMany(mappedBy = "vault", fetch = FetchType.LAZY) - Set accessTokens = new HashSet<>(); + private Set accessTokens = new HashSet<>(); @Column(name = "name", nullable = false) - String name; + private String name; @Column(name = "salt") - String salt; + private String salt; @Column(name = "iterations") - Integer iterations; + private Integer iterations; @Column(name = "masterkey") - String masterkey; + private String masterkey; @Column(name = "auth_pubkey") - String authenticationPublicKey; + private String authenticationPublicKey; @Column(name = "auth_prvkey") - String authenticationPrivateKey; + private String authenticationPrivateKey; @Column(name = "creation_time", nullable = false) - Instant creationTime; + private Instant creationTime; @Column(name = "description") - String description; + private String description; @Column(name = "archived", nullable = false) - boolean archived; + private boolean archived; public Optional getAuthenticationPublicKeyOptional() { if (authenticationPublicKey == null) { @@ -252,8 +252,8 @@ public int hashCode() { public String toString() { return "Vault{" + "id='" + id + '\'' + - ", members=" + directMembers.stream().map(m -> m.id).collect(Collectors.joining(", ")) + - ", accessToken=" + accessTokens.stream().map(a -> a.id.toString()).collect(Collectors.joining(", ")) + + ", members=" + directMembers.stream().map(Authority::getId).collect(Collectors.joining(", ")) + + ", accessToken=" + accessTokens.stream().map(a -> a.getId().toString()).collect(Collectors.joining(", ")) + ", name='" + name + '\'' + ", archived='" + archived + '\'' + ", salt='" + salt + '\'' + 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 979738e64..3cf477e56 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/VaultAccess.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/VaultAccess.java @@ -33,21 +33,21 @@ public class VaultAccess { @EmbeddedId - VaultAccess.Id id = new VaultAccess.Id(); + private VaultAccess.Id id = new VaultAccess.Id(); @ManyToOne @MapsId("vaultId") @JoinColumn(name = "vault_id") - Vault vault; + private Vault vault; @ManyToOne @MapsId("authorityId") @JoinColumn(name = "authority_id") - Authority authority; + private Authority authority; @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) - Role role; + private Role role; public enum Role { /** 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 b42b51f72..e19e16302 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 @@ -48,13 +48,13 @@ public class AuditEvent { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "audit_event_id_seq") @Column(name = "id", nullable = false, updatable = false) - long id; + private long id; @Column(name = "timestamp", nullable = false, updatable = false) - Instant timestamp; + private Instant timestamp; @Column(name = "type", nullable = false, insertable = false, updatable = false) - String type; + private String type; public long getId() { return id; 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 42f6ace78..6117336a9 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 @@ -18,17 +18,17 @@ public class DeviceRegisteredEvent extends AuditEvent { public static final String TYPE = "DEVICE_REGISTER"; @Column(name = "registered_by") - String registeredBy; + private String registeredBy; @Column(name = "device_id") - String deviceId; + private String deviceId; @Column(name = "device_name") - String deviceName; + private String deviceName; @Column(name = "device_type") @Enumerated(EnumType.STRING) - Device.Type deviceType; + private Device.Type deviceType; public String getRegisteredBy() { return registeredBy; @@ -76,6 +76,6 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(id, registeredBy, deviceId, deviceName, deviceType); + return Objects.hash(super.getId(), registeredBy, deviceId, deviceName, 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 597236f0b..71ef0bc0c 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 @@ -15,10 +15,10 @@ public class DeviceRemovedEvent extends AuditEvent { public static final String TYPE = "DEVICE_REMOVE"; @Column(name = "removed_by") - String removedBy; + private String removedBy; @Column(name = "device_id") - String deviceId; + private String deviceId; public String getRemovedBy() { return removedBy; @@ -48,7 +48,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(id, removedBy, deviceId); + return Objects.hash(super.getId(), removedBy, deviceId); } } 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 3b809d822..4314e0025 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 @@ -16,13 +16,13 @@ public class VaultAccessGrantedEvent extends AuditEvent { public static final String TYPE = "VAULT_ACCESS_GRANT"; @Column(name = "granted_by") - String grantedBy; + private String grantedBy; @Column(name = "vault_id") - UUID vaultId; + private UUID vaultId; @Column(name = "authority_id") - String authorityId; + private String authorityId; public String getGrantedBy() { return grantedBy; @@ -61,7 +61,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(id, grantedBy, vaultId, authorityId); + return Objects.hash(super.getId(), grantedBy, vaultId, 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 a85cf844d..8a1e9863b 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 @@ -16,16 +16,16 @@ public class VaultCreatedEvent extends AuditEvent { public static final String TYPE = "VAULT_CREATE"; @Column(name = "created_by") - String createdBy; + private String createdBy; @Column(name = "vault_id") - UUID vaultId; + private UUID vaultId; @Column(name = "vault_name") - String vaultName; + private String vaultName; @Column(name = "vault_description") - String vaultDescription; + private String vaultDescription; public String getCreatedBy() { return createdBy; @@ -73,7 +73,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(id, createdBy, vaultId, vaultName, vaultDescription); + return Objects.hash(super.getId(), createdBy, vaultId, vaultName, 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 9fefac00b..ed25d1f3c 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 @@ -18,14 +18,14 @@ public class VaultKeyRetrievedEvent extends AuditEvent { public static final String TYPE = "VAULT_KEY_RETRIEVE"; @Column(name = "retrieved_by") - String retrievedBy; + private String retrievedBy; @Column(name = "vault_id") - UUID vaultId; + private UUID vaultId; @Column(name = "result") @Enumerated(EnumType.STRING) - Result result; + private Result result; public String getRetrievedBy() { return retrievedBy; @@ -64,7 +64,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(id, retrievedBy, vaultId, result); + return Objects.hash(super.getId(), retrievedBy, vaultId, result); } public enum Result { 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 70a61477b..0472f5593 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 @@ -19,17 +19,17 @@ public class VaultMemberAddedEvent extends AuditEvent { public static final String TYPE = "VAULT_MEMBER_ADD"; @Column(name = "added_by") - String addedBy; + private String addedBy; @Column(name = "vault_id") - UUID vaultId; + private UUID vaultId; @Column(name = "authority_id") - String authorityId; + private String authorityId; @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) - VaultAccess.Role role; + private VaultAccess.Role role; public String getAddedBy() { return addedBy; @@ -77,7 +77,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(id, addedBy, vaultId, authorityId, role); + return Objects.hash(super.getId(), addedBy, vaultId, authorityId, role); } } 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 48e07909f..de236c9c5 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 @@ -16,13 +16,13 @@ public class VaultMemberRemovedEvent extends AuditEvent { public static final String TYPE = "VAULT_MEMBER_REMOVE"; @Column(name = "removed_by") - String removedBy; + private String removedBy; @Column(name = "vault_id") - UUID vaultId; + private UUID vaultId; @Column(name = "authority_id") - String authorityId; + private String authorityId; public String getRemovedBy() { return removedBy; @@ -61,7 +61,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(id, removedBy, vaultId, authorityId); + return Objects.hash(super.getId(), removedBy, vaultId, 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 4092d5c68..c5cf8c479 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 @@ -19,17 +19,17 @@ public class VaultMemberUpdatedEvent extends AuditEvent { public static final String TYPE = "VAULT_MEMBER_UPDATE"; @Column(name = "updated_by") - String updatedBy; + private String updatedBy; @Column(name = "vault_id") - UUID vaultId; + private UUID vaultId; @Column(name = "authority_id") - String authorityId; + private String authorityId; @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) - VaultAccess.Role role; + private VaultAccess.Role role; public String getUpdatedBy() { return updatedBy; @@ -77,7 +77,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(id, updatedBy, vaultId, authorityId, role); + return Objects.hash(super.getId(), updatedBy, vaultId, authorityId, role); } } 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 3d5e34a8b..a1344b312 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 @@ -16,10 +16,10 @@ public class VaultOwnershipClaimedEvent extends AuditEvent { public static final String TYPE = "VAULT_OWNERSHIP_CLAIM"; @Column(name = "claimed_by") - String claimedBy; + private String claimedBy; @Column(name = "vault_id") - UUID vaultId; + private UUID vaultId; public String getClaimedBy() { return claimedBy; @@ -49,7 +49,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(id, claimedBy, vaultId); + return Objects.hash(super.getId(), claimedBy, 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 e961a6ed1..d6ea6c382 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 @@ -1,13 +1,10 @@ package org.cryptomator.hub.entities.events; -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.Table; -import java.time.Instant; import java.util.Objects; import java.util.UUID; @@ -19,19 +16,19 @@ public class VaultUpdatedEvent extends AuditEvent { public static final String TYPE = "VAULT_UPDATE"; @Column(name = "updated_by") - String updatedBy; + private String updatedBy; @Column(name = "vault_id") - UUID vaultId; + private UUID vaultId; @Column(name = "vault_name") - String vaultName; + private String vaultName; @Column(name = "vault_description") - String vaultDescription; + private String vaultDescription; @Column(name = "vault_archived") - boolean vaultArchived; + private boolean vaultArchived; public String getUpdatedBy() { return updatedBy; @@ -88,7 +85,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(id, updatedBy, vaultId, vaultName, vaultDescription, vaultArchived); + return Objects.hash(super.getId(), updatedBy, vaultId, vaultName, vaultDescription, vaultArchived); } } diff --git a/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java b/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java index e0c3ae90f..cf590c65e 100644 --- a/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java +++ b/backend/src/test/java/org/cryptomator/hub/entities/EntityIT.java @@ -36,7 +36,7 @@ public void removingUserCascadesToAccess() throws SQLException { } var deleted = userRepo.deleteById("user999"); - var matchAfter = accessTokenRepo.findAll().stream().anyMatch(a -> "user999".equals(a.user.id)); + var matchAfter = accessTokenRepo.findAll().stream().anyMatch(a -> "user999".equals(a.getUser().getId())); Assertions.assertTrue(deleted); Assertions.assertFalse(matchAfter); } @@ -46,8 +46,8 @@ public void removingUserCascadesToAccess() throws SQLException { @DisplayName("Retrieve the correct token when a user has access to multiple vaults") public void testGetCorrectTokenForDeviceWithAcessToMultipleVaults() { var token = accessTokenRepo.unlock(UUID.fromString("7E57C0DE-0000-4000-8000-000100001111"), "user1"); - Assertions.assertEquals(UUID.fromString("7E57C0DE-0000-4000-8000-000100001111"), token.vault.id); - Assertions.assertEquals("user1", token.user.id); - Assertions.assertEquals("jwe.jwe.jwe.vault1.user1", token.vaultKey); + Assertions.assertEquals(UUID.fromString("7E57C0DE-0000-4000-8000-000100001111"), token.getVault().getId()); + Assertions.assertEquals("user1", token.getUser().getId()); + Assertions.assertEquals("jwe.jwe.jwe.vault1.user1", token.getVaultKey()); } } \ No newline at end of file From c3341aa894e2f2382a83683675d48845e5688f6d Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 10 Apr 2024 10:48:49 +0200 Subject: [PATCH 47/54] ensure correct test execution order --- .../org/cryptomator/hub/RollbackTestIT.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/src/test/java/org/cryptomator/hub/RollbackTestIT.java b/backend/src/test/java/org/cryptomator/hub/RollbackTestIT.java index 98938d4a6..e2fc8733d 100644 --- a/backend/src/test/java/org/cryptomator/hub/RollbackTestIT.java +++ b/backend/src/test/java/org/cryptomator/hub/RollbackTestIT.java @@ -6,8 +6,11 @@ import org.cryptomator.hub.rollback.DBRollbackAfter; import org.flywaydb.core.Flyway; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import java.sql.SQLException; @@ -20,11 +23,13 @@ public class RollbackTestIT { AgroalDataSource dataSource; @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class WithFlywayCleanup { @Test + @Order(1) @DBRollbackAfter - public void test1() throws SQLException { + public void changeDB() throws SQLException { try (var c = dataSource.getConnection(); var s = c.createStatement()) { s.execute(""" UPDATE "settings" @@ -35,7 +40,8 @@ public void test1() throws SQLException { } @Test - public void test2() throws SQLException { + @Order(2) + public void testDB() throws SQLException { try (var c = dataSource.getConnection(); var s = c.createStatement()) { var result = s.executeQuery(""" SELECT * @@ -49,10 +55,12 @@ public void test2() throws SQLException { } @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class NoCleanup { @Test - public void test1() throws SQLException { + @Order(1) + public void changeDB() throws SQLException { try (var c = dataSource.getConnection(); var s = c.createStatement()) { s.execute(""" UPDATE "settings" @@ -63,7 +71,8 @@ public void test1() throws SQLException { } @Test - public void test2() throws SQLException { + @Order(2) + public void testDB() throws SQLException { try (var c = dataSource.getConnection(); var s = c.createStatement()) { var result = s.executeQuery(""" SELECT * From 1791f4f050f4f0580b862751715e060b6097679e Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 12 Apr 2024 15:54:55 +0200 Subject: [PATCH 48/54] reduce usage of sql in tests --- .../cryptomator/hub/api/VaultResourceIT.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java index a76afc4a2..143699ebd 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java +++ b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java @@ -3,6 +3,7 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import io.agroal.api.AgroalDataSource; +import io.quarkus.narayana.jta.QuarkusTransaction; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.security.TestSecurity; import io.quarkus.test.security.oidc.Claim; @@ -12,6 +13,7 @@ import jakarta.inject.Inject; import jakarta.validation.Validator; import org.cryptomator.hub.entities.EffectiveVaultAccess; +import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.rollback.DBRollbackAfter; import org.cryptomator.hub.rollback.DBRollbackBefore; import org.flywaydb.core.Flyway; @@ -63,6 +65,8 @@ public class VaultResourceIT { @Inject EffectiveVaultAccess.Repository effectiveVaultAccessRepo; @Inject + Vault.Repository vaultRepo; + @Inject Validator validator; @Inject public Flyway flyway; @@ -838,23 +842,24 @@ public class ClaimOwnership { private static Algorithm JWT_ALG; @BeforeAll - public void setup() throws GeneralSecurityException, SQLException { + public void setup() throws GeneralSecurityException { var keyPairGen = KeyPairGenerator.getInstance("EC"); keyPairGen.initialize(new ECGenParameterSpec("secp384r1")); var keyPair = keyPairGen.generateKeyPair(); JWT_ALG = Algorithm.ECDSA384((ECPrivateKey) keyPair.getPrivate()); - var authPubKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()); - try (var c = dataSource.getConnection(); var s = c.createStatement()) { - s.execute(""" - INSERT INTO "vault" ("id", "name", "description", "creation_time", "salt", "iterations", "masterkey", "auth_pubkey", "auth_prvkey", "archived") - VALUES - ('7E57C0DE-0000-4000-8000-000100009999', 'ownership-test-vault', 'Testing ownership.', '2020-02-20 20:20:20', NULL, NULL , NULL, - '%s', - NULL, - FALSE); - """.formatted(authPubKey)); - } + //transaction context required for persistence + QuarkusTransaction.requiringNew() + .timeout(10) + .call(() -> { + Vault v = new Vault(); + v.setId(UUID.fromString("7E57C0DE-0000-4000-8000-000100009999")); + v.setName("ownership-test-vault"); + v.setCreationTime(Instant.now()); + v.setAuthenticationPublicKey(Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded())); + vaultRepo.persist(v); + return 0; + }); } @Test @@ -1021,7 +1026,7 @@ public void testClaimOwnershipAlreadyClaimed() { } @AfterAll - public void reset() throws SQLException { + public void cleanup() throws SQLException { try (var c = dataSource.getConnection(); var s = c.createStatement()) { s.execute(""" DELETE FROM "vault" WHERE "id" = '7E57C0DE-0000-4000-8000-000100009999'; From f135a31c19b88918de69e101cb6aa08df6ee8fbf Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 12 Apr 2024 16:21:20 +0200 Subject: [PATCH 49/54] refactor License refresh skip condition into inner class --- .../hub/license/LicenseHolder.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) 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 a2c6dfe59..6e00ab18f 100644 --- a/backend/src/main/java/org/cryptomator/hub/license/LicenseHolder.java +++ b/backend/src/main/java/org/cryptomator/hub/license/LicenseHolder.java @@ -24,7 +24,7 @@ import java.util.Optional; @ApplicationScoped -public class LicenseHolder implements Scheduled.SkipPredicate { +public class LicenseHolder { private static final int SELFHOSTED_NOLICENSE_SEATS = 5; private static final int MANAGED_NOLICENSE_SEATS = 0; @@ -106,7 +106,7 @@ public void set(String token) throws JWTVerificationException { /** * Attempts to refresh the Hub licence every day between 01:00:00 and 02:00:00 AM UTC if claim refreshURL is present. */ - @Scheduled(cron = "0 0 1 * * ?", timeZone = "UTC", concurrentExecution = Scheduled.ConcurrentExecution.SKIP, skipExecutionIf = LicenseHolder.class) + @Scheduled(cron = "0 0 1 * * ?", timeZone = "UTC", concurrentExecution = Scheduled.ConcurrentExecution.SKIP, skipExecutionIf = LicenseHolder.LicenseRefreshSkipper.class) void refreshLicense() throws InterruptedException { randomMinuteSleeper.sleep(); // add random sleep between [0,59]min to reduce infrastructure load var refreshUrlClaim = get().getClaim("refreshUrl"); @@ -144,12 +144,6 @@ String requestLicenseRefresh(URI refreshUrl, String licenseToken) throws Interru } } - //necessary for skipExecutionIf - @Override - public boolean test(ScheduledExecution execution) { - return license == null; - } - public DecodedJWT get() { return license; } @@ -207,4 +201,16 @@ static class LicenseRefreshFailedException extends RuntimeException { this.body = body; } } + + @ApplicationScoped + public static class LicenseRefreshSkipper implements Scheduled.SkipPredicate { + + @Inject + LicenseHolder licenseHolder; + + @Override + public boolean test(ScheduledExecution execution) { + return licenseHolder.license == null; + } + } } From 6a08760f3c06c6d4a1162287ea74fd16343e1433 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 12 Apr 2024 16:24:09 +0200 Subject: [PATCH 50/54] only allow single result when quering for legacy access token --- .../main/java/org/cryptomator/hub/api/VaultResource.java | 7 ++++--- .../org/cryptomator/hub/entities/LegacyAccessToken.java | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) 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 9a26905fc..14568163c 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -8,6 +8,7 @@ import jakarta.annotation.Nullable; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; +import jakarta.persistence.NoResultException; import jakarta.transaction.Transactional; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; @@ -277,13 +278,13 @@ public Response legacyUnlock(@PathParam("vaultId") UUID vaultId, @PathParam("dev throw new PaymentRequiredException("Number of effective vault users exceeds available license seats"); } - var access = legacyAccessTokenRepo.unlock(vaultId, deviceId, jwt.getSubject()); - if (access != null) { + try { + var access = legacyAccessTokenRepo.unlock(vaultId, deviceId, jwt.getSubject()); eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); var subscriptionStateHeaderName = "Hub-Subscription-State"; var subscriptionStateHeaderValue = license.isSet() ? "ACTIVE" : "INACTIVE"; // license expiration is not checked here, because it is checked in the ActiveLicense filter return Response.ok(access.getJwe()).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); - } else { + } catch (NoResultException e){ eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); throw new ForbiddenException("Access to this device not granted."); } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java b/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java index d36c49e1b..f0ba29d9e 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/LegacyAccessToken.java @@ -140,7 +140,7 @@ public static class Repository implements PanacheRepositoryBase getByDeviceAndOwner(String deviceId, String userId) { From f232b7df7c9c7ec14da07e5f66c502218a7b48d1 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 26 Apr 2024 18:25:05 +0200 Subject: [PATCH 51/54] adjust LicenseResource to repo pattern --- .../java/org/cryptomator/hub/api/LicenseResource.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 ed112d2b6..dfc1efa0c 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/LicenseResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/LicenseResource.java @@ -22,6 +22,9 @@ public class LicenseResource { @Inject LicenseHolder licenseHolder; + @Inject + EffectiveVaultAccess.Repository effectiveVaultAccessRepo; + @GET @Path("/user-info") @Produces(MediaType.APPLICATION_JSON) @@ -29,17 +32,16 @@ public class LicenseResource { @Operation(summary = "Get license information for regular users", description = "Information includes the licensed seats, the already used seats and if defined, the license expiration date.") @APIResponse(responseCode = "200") public LicenseUserInfoDto get() { - return LicenseUserInfoDto.create(licenseHolder); + int usedSeats = (int) effectiveVaultAccessRepo.countSeatOccupyingUsers(); + return LicenseUserInfoDto.create(licenseHolder, usedSeats); } - public record LicenseUserInfoDto(@JsonProperty("licensedSeats") Integer licensedSeats, @JsonProperty("usedSeats") Integer usedSeats, @JsonProperty("expiresAt") Instant expiresAt) { - public static LicenseUserInfoDto create(LicenseHolder licenseHolder) { + public static LicenseUserInfoDto create(LicenseHolder licenseHolder, int usedSeats) { var licensedSeats = (int) licenseHolder.getSeats(); - var usedSeats = (int) EffectiveVaultAccess.countSeatOccupyingUsers(); var expiresAt = Optional.ofNullable(licenseHolder.get()).map(DecodedJWT::getExpiresAtAsInstant).orElse(null); return new LicenseUserInfoDto(licensedSeats, usedSeats, expiresAt); } From df86df564db8ceef32d5e9b295733eba66ca01bc Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 26 Apr 2024 18:52:50 +0200 Subject: [PATCH 52/54] replace `assert`s with `Assumption`s --- .../cryptomator/hub/api/VaultResourceIT.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java index 552358231..cd2899643 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java +++ b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java @@ -20,6 +20,7 @@ import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer; @@ -697,7 +698,7 @@ public void setup() throws SQLException { @Order(0) @DisplayName("POST /vaults/7E57C0DE-0000-4000-8000-000100001111/access-tokens returns 402 for [user91, user92, user93, user94]") public void grantAccessExceedingSeats() { - assert effectiveVaultAccessRepo.countSeatOccupyingUsers() == 2; + Assumptions.assumeTrue(effectiveVaultAccessRepo.countSeatOccupyingUsers() == 2); var body = Map.of( "user91", "jwe.jwe.jwe.vault1.user91", // "user92", "jwe.jwe.jwe.vault1.user92", // @@ -714,7 +715,7 @@ public void grantAccessExceedingSeats() { @Order(1) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/groups/group91 returns 402") public void addGroupToVaultExceedingSeats() { - assert effectiveVaultAccessRepo.countSeatOccupyingUsers() == 2; + Assumptions.assumeTrue(effectiveVaultAccessRepo.countSeatOccupyingUsers() == 2); given().when().put("/vaults/{vaultId}/groups/{groupId}", "7E57C0DE-0000-4000-8000-000100001111", "group91") .then().statusCode(402); @@ -725,7 +726,7 @@ public void addGroupToVaultExceedingSeats() { @ParameterizedTest(name = "Adding user {0} succeeds") @CsvSource(value = {"0,user91", "1,user92", "2,user93"}) public void addUserToVaultNotExceedingSeats(String run, String userId) { - assert effectiveVaultAccessRepo.countSeatOccupyingUsers() == (2 + Integer.valueOf(run)); + Assumptions.assumeTrue(effectiveVaultAccessRepo.countSeatOccupyingUsers() == 2 + Integer.parseInt(run)); given().when().put("/vaults/{vaultId}/users/{usersId}", "7E57C0DE-0000-4000-8000-000100001111", userId) .then().statusCode(201); @@ -735,7 +736,7 @@ public void addUserToVaultNotExceedingSeats(String run, String userId) { @Order(3) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/users/user94 returns 402") public void addUserToVaultExceedingSeats() { - assert effectiveVaultAccessRepo.countSeatOccupyingUsers() == 5; + Assumptions.assumeTrue(effectiveVaultAccessRepo.countSeatOccupyingUsers() == 5); given().when().put("/vaults/{vaultId}/users/{usersId}", "7E57C0DE-0000-4000-8000-000100001111", "user94") .then().statusCode(402); @@ -745,7 +746,7 @@ public void addUserToVaultExceedingSeats() { @Order(4) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111 (as user1) returns 200 with only updated name, description and archive flag, despite exceeding license") public void testUpdateVaultDespiteLicenseExceeded() { - assert effectiveVaultAccessRepo.countSeatOccupyingUsers() == 5; + Assumptions.assumeTrue(effectiveVaultAccessRepo.countSeatOccupyingUsers() == 5); var vaultId = "7E57C0DE-0000-4000-8000-000100001111"; var vaultDto = new VaultResource.VaultDto(UUID.fromString(vaultId), "Vault 1", "This is a testvault.", false, Instant.parse("2222-11-11T11:11:11Z"), "someVaule", -1, "doNotUpdate", "doNotUpdate", "doNotUpdate"); @@ -771,7 +772,7 @@ public void testCreateVaultExceedingSeats() throws SQLException { """); } - assert effectiveVaultAccessRepo.countSeatOccupyingUsers() > 5; + Assumptions.assumeTrue(effectiveVaultAccessRepo.countSeatOccupyingUsers() > 5); var uuid = UUID.fromString("7E57C0DE-0000-4000-8000-0001FFFF3333"); var vaultDto = new VaultResource.VaultDto(uuid, "My Vault", "Test vault 4", false, Instant.parse("2112-12-21T21:12:21Z"), "masterkey3", 42, "NaCl", "authPubKey3", "authPrvKey3"); @@ -783,8 +784,8 @@ public void testCreateVaultExceedingSeats() throws SQLException { @Test @Order(7) @DisplayName("unlock/legacyUnlock is granted, if (effective vault user) > license seats but (effective vault user with access token) <= license seat") - public void testUnlockAllowedExceedingLicenseSoftLimit() throws SQLException { - assert effectiveVaultAccessRepo.countSeatOccupyingUsersWithAccessToken() <= 5; + public void testUnlockAllowedExceedingLicenseSoftLimit() { + Assumptions.assumeTrue(effectiveVaultAccessRepo.countSeatOccupyingUsersWithAccessToken() <= 5); when().get("/vaults/{vaultId}/access-token", "7E57C0DE-0000-4000-8000-000100001111") .then().statusCode(200); @@ -809,7 +810,7 @@ public void testUnockBlockedExceedingLicenseHardLimit() throws SQLException { VALUES ('user94', '7E57C0DE-0000-4000-8000-000100001111', 'jwe.jwe.jwe.vault1.user94'); """); } - assert effectiveVaultAccessRepo.countSeatOccupyingUsersWithAccessToken() > 5; + Assumptions.assumeTrue(effectiveVaultAccessRepo.countSeatOccupyingUsersWithAccessToken() > 5); when().get("/vaults/{vaultId}/access-token", "7E57C0DE-0000-4000-8000-000100001111") .then().statusCode(402); From 359257c95e764e3e5754ec4e5cf47a325bf24ed7 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 26 Apr 2024 18:53:27 +0200 Subject: [PATCH 53/54] more user-friendly exception messages --- .../src/main/java/org/cryptomator/hub/api/VaultResource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 12442783f..5b492647e 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -175,7 +175,7 @@ public Response addUser(@PathParam("vaultId") UUID vaultId, @PathParam("userId") return addAuthority(vault, user, role); } //otherwise block - throw new PaymentRequiredException("Number of effective vault users greater than or equal to the available license seats"); + throw new PaymentRequiredException("License seats exceeded. Cannot add more users."); } @PUT @@ -198,7 +198,7 @@ public Response addGroup(@PathParam("vaultId") UUID vaultId, @PathParam("groupId //usersInGroup - usersInGroupAndPartOfAtLeastOneVault + usersOfAtLeastOneVault if (userRepo.countEffectiveGroupUsers(groupId) - effectiveVaultAccessRepo.countSeatOccupyingUsersOfGroup(groupId) + effectiveVaultAccessRepo.countSeatOccupyingUsers() > license.getSeats()) { - throw new PaymentRequiredException("Number of effective vault users greater than or equal to the available license seats"); + throw new PaymentRequiredException("Adding this group would exceed available license seats."); } return addAuthority(vault, group, role); From d7e8e09e7392f4b02f7e5320e1a733f2d85d3602 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sun, 28 Apr 2024 10:50:20 +0200 Subject: [PATCH 54/54] simplify logic --- .../java/org/cryptomator/hub/api/VaultResource.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) 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 5b492647e..53955810f 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -78,6 +78,7 @@ public class VaultResource { @Inject EffectiveVaultAccess.Repository effectiveVaultAccessRepo; @Inject + @Deprecated LegacyAccessToken.Repository legacyAccessTokenRepo; @Inject Vault.Repository vaultRepo; @@ -166,16 +167,12 @@ public Response addUser(@PathParam("vaultId") UUID vaultId, @PathParam("userId") var vault = vaultRepo.findById(vaultId); // should always be found, since @VaultRole filter would have triggered var user = userRepo.findByIdOptional(userId).orElseThrow(NotFoundException::new); var usedSeats = effectiveVaultAccessRepo.countSeatOccupyingUsers(); - //check if license seats are free - if (usedSeats < license.getSeats()) { - return addAuthority(vault, user, role); - } - // else check, if all seats are taken, but the person to add is already sitting - if (usedSeats == license.getSeats() && effectiveVaultAccessRepo.isUserOccupyingSeat(userId)) { + if (usedSeats < license.getSeats() // free seats available + || effectiveVaultAccessRepo.isUserOccupyingSeat(userId)) { // or user already sitting return addAuthority(vault, user, role); + } else { + throw new PaymentRequiredException("License seats exceeded. Cannot add more users."); } - //otherwise block - throw new PaymentRequiredException("License seats exceeded. Cannot add more users."); } @PUT