diff --git a/backend/setup/aws_static/aws_static_profile.json b/backend/setup/aws_static/aws_static_profile.json index 7601ddca6..f561e32a9 100644 --- a/backend/setup/aws_static/aws_static_profile.json +++ b/backend/setup/aws_static/aws_static_profile.json @@ -1,4 +1,5 @@ { + "id": "72736C19-283C-49D3-80A5-AB74B5202543", "name": "AWS S3 static", "region": "eu-central-1", "protocol": "s3" diff --git a/backend/setup/aws_sts/aws_sts_profile.json b/backend/setup/aws_sts/aws_sts_profile.json index 8bf150f35..2c85b191b 100644 --- a/backend/setup/aws_sts/aws_sts_profile.json +++ b/backend/setup/aws_sts/aws_sts_profile.json @@ -1,4 +1,5 @@ { + "id": "844BD517-96D4-4787-BCFA-238E103149F6", "name": "AWS S3 STS", "bucketPrefix": "cipherduck", "stsRoleArnHub": "arn:aws:iam::930717317329:role/cipherduck-createbucket", diff --git a/backend/setup/minio_static/minio_static_profile.json b/backend/setup/minio_static/minio_static_profile.json index b318d7479..e7300ac32 100644 --- a/backend/setup/minio_static/minio_static_profile.json +++ b/backend/setup/minio_static/minio_static_profile.json @@ -1,6 +1,6 @@ { + "id":"71B910E0-2ECC-46DE-A871-8DB28549677E", "name": "MinIO S3 static", - "s3Endpoint": "http://minio:9000", "protocol": "s3", "withPathStyleAccessEnabled": "true", "hostname": "minio", diff --git a/backend/setup/minio_sts/minio_sts_profile.json b/backend/setup/minio_sts/minio_sts_profile.json index cde0b24eb..6c19c37d4 100644 --- a/backend/setup/minio_sts/minio_sts_profile.json +++ b/backend/setup/minio_sts/minio_sts_profile.json @@ -1,4 +1,5 @@ { + "id": "732D43FA-3716-46C4-B931-66EA5405EF1C", "name": "MinIO S3 STS", "bucketPrefix": "cipherduck", "region": "eu-central-1", diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/CipherduckConfig.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/CipherduckConfig.java index 907c557d1..5175e3497 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/CipherduckConfig.java +++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/CipherduckConfig.java @@ -40,8 +40,6 @@ public class CipherduckConfig { @Inject OidcConfigurationMetadata oidcConfData; - public List inMemoryStorageConfigs = new ArrayList<>(); - String replacePrefix(String str, String prefix, String replacement) { int index = str.indexOf(prefix); if (index == 0) { diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileDto.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileDto.java index 6cdb1991b..a23ef0748 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileDto.java @@ -3,22 +3,29 @@ import com.amazonaws.regions.Regions; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Arrays; import java.util.List; import java.util.UUID; -// TODO https://github.com/shift7-ch/cipherduck-hub/issues/4 (R3) update documentation -public class StorageProfileDto { +@Entity +@Table(name = "storage_profile") +public class StorageProfileDto extends PanacheEntityBase { public enum Protocol { s3("s3"), s3sts("s3-sts"); private final String protocol; - private Protocol(final String protocol){ + private Protocol(final String protocol) { this.protocol = protocol; } + @JsonValue public String getProtocol() { return protocol; @@ -27,53 +34,52 @@ public String getProtocol() { } - // (1) bucket creation - // TODO https://github.com/shift7-ch/cipherduck-hub/issues/4 (R3) auto-generate in DB - + @Id + @Column(name = "id", nullable = false) @JsonProperty(value = "id", required = true) UUID id; // clients will use this as vendor in profile and provider in vault bookmark @JsonProperty(value = "name", required = true) String name; - @JsonProperty(value = "bucketPrefix") - String bucketPrefix; // not required if we do not create bucket (non-STS case) - - @JsonProperty("stsRoleArnClient") - String stsRoleArnClient; // not required if we do not create bucket (non-STS case) + // (1) bucket creation, template upload and client profile + @JsonProperty("scheme") + String scheme; // defaults to AWS - @JsonProperty("stsRoleArnHub") // not required if we do not create bucket (non-STS case) - String stsRoleArnHub; + @JsonProperty("hostname") + String hostname; // defaults to AWS - @JsonProperty("stsEndpoint") - String stsEndpoint; // not required if we do not create bucket (non-STS case) + @JsonProperty("port") + Integer port; // defaults to AWS @JsonProperty(value = "region") String region = "us-east-1"; // default region selected in the frontend/client @JsonProperty(value = "regions") - List regions = Arrays.stream(Regions.values()).map(r -> r.getName()).toList(); // defaults to full list injected - + List regions = Arrays.stream(Regions.values()).map(r -> r.getName()).toList(); // defaults to full AWS list @JsonProperty(value = "withPathStyleAccessEnabled") - Boolean withPathStyleAccessEnabled = false; // not required if we do not create bucket (non-STS case) + Boolean withPathStyleAccessEnabled = false; - // (2) bucket creation and client profile - @JsonProperty("scheme") - String scheme; // defaults to AWS + // (2) bucket creation only (i.e. STS-case) + @JsonProperty(value = "bucketPrefix") + String bucketPrefix; - @JsonProperty("hostname") - String hostname; // defaults to AWS + @JsonProperty("stsRoleArnClient") + String stsRoleArnClient; - @JsonProperty("port") // defaults to AWS - Integer port; + @JsonProperty("stsRoleArnHub") + String stsRoleArnHub; - @JsonProperty(value = "protocol") - Protocol protocol = Protocol.s3sts; + @JsonProperty("stsEndpoint") + String stsEndpoint; // defaults to AWS // (3) client profile + // (3a) client profile attributes + @JsonProperty(value = "protocol") + Protocol protocol = Protocol.s3sts; @JsonProperty(value = "oauthClientId") String oauthClientId; // injected from hub config if STS @@ -84,7 +90,7 @@ public String getProtocol() { String oauthAuthorizationUrl; // injected from hub config if STS - // (3) client profile custom properties + // (3b) client profile custom properties @JsonProperty(value = "stsRoleArn") String stsRoleArn; // token exchange diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileResource.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileResource.java index eadc8a1dc..ee76a0746 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileResource.java @@ -1,19 +1,24 @@ package org.cryptomator.hub.api.cipherduck; import jakarta.annotation.security.PermitAll; +import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.transaction.Transactional; +import jakarta.ws.rs.ClientErrorException; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.hibernate.exception.ConstraintViolationException; +import java.net.URI; import java.util.List; -import java.util.UUID; +import java.util.stream.Collectors; @Path("/storageprofile") public class StorageProfileResource { @@ -25,33 +30,40 @@ public class StorageProfileResource { @PUT @Path("/") + // TODO https://github.com/shift7-ch/cipherduck-hub/issues/4 (R3) restrict to admin after refctoring is done + // @RolesAllowed("admin") @PermitAll + @Transactional @Consumes(MediaType.APPLICATION_JSON) - public void uploadStorageProfile(StorageProfileDto c) { - c.withId(UUID.randomUUID()); - // TODO https://github.com/shift7-ch/cipherduck-hub/issues/4 (R3) StorageProfileDto is not very transparent from the admin perspective - which fields are required for S3 static and which for S3 STS - we need some kind of meta-model (which could also be used for configuring which fields to show/hide in the ui). + public Response uploadStorageProfile(StorageProfileDto c) { + // TODO https://github.com/shift7-ch/cipherduck-hub/issues/4 (R3) protocol-specific validations? switch (c.protocol) { case s3: break; case s3sts: break; } - cipherduckConfig.inMemoryStorageConfigs.add(c); - + try { + c.persistAndFlush(); + return Response.created(URI.create(".")).build(); + } catch (ConstraintViolationException e) { + throw new ClientErrorException(Response.Status.CONFLICT, e); + } } @GET @Path("/") - // TODO https://github.com/shift7-ch/cipherduck-hub/issues/4 (R3) restrict to admin after refctoring is done -// @RolesAllowed("admin") + // TODO https://github.com/shift7-ch/cipherduck-hub/issues/4 (R3) restrict to user after refctoring is done + // @RolesAllowed("user") @PermitAll @Produces(MediaType.APPLICATION_JSON) @Transactional @Operation(summary = "get configs for storage backends", description = "get list of configs for storage backends") @APIResponse(responseCode = "200", description = "uploaded storage configuration") public List getStorageProfiles() { - for (StorageProfileDto storageProfileDto : cipherduckConfig.inMemoryStorageConfigs) { + List storageProfiles = StorageProfileDto.findAll().stream().collect(Collectors.toList()); + for (StorageProfileDto storageProfileDto : storageProfiles) { // inject OAuth endpoints if STS if (storageProfileDto.stsRoleArn() != null) { storageProfileDto @@ -62,7 +74,20 @@ public List getStorageProfiles() { ; } } - return cipherduckConfig.inMemoryStorageConfigs; + return storageProfiles; + } + + // TODO https://github.com/shift7-ch/cipherduck-hub/issues/4 (R3) refactor into meta service for getting which values are required for which protocol? + @GET + @Path("/meta") + @RolesAllowed("admin") + @Produces(MediaType.APPLICATION_JSON) + @Transactional + @Operation(summary = "get configs for storage backends", description = "get list of configs for storage backends") + @APIResponse(responseCode = "200", description = "uploaded storage configuration") + public VaultJWEBackendDto getVaultJWEBackendDto(final StorageProfileDto.Protocol protocol) { + // N.B. temporary workaround to have VaultJWEBackendDto exposed in openapi.json for now.... + return null; } diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageResource.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageResource.java index b6a4aabbc..07f477f42 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageResource.java @@ -51,7 +51,7 @@ public class StorageResource { @APIResponse(responseCode = "400", description = "Could not create bucket") public Response createBucket(@PathParam("vaultId") UUID vaultId, StorageDto storage) { - final Map storageConfigs = cipherduckConfig.inMemoryStorageConfigs.stream().collect(Collectors.toMap(StorageProfileDto::id, Function.identity())); + final Map storageConfigs = StorageProfileDto.findAll().stream().collect(Collectors.toMap(StorageProfileDto::id, Function.identity())); final StorageProfileDto storageProfile = storageConfigs.get(storage.storageConfigId()); // N.B. if the bucket already exists, this will fail, so we do not prevent calling this method several times. diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEBackendDto.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEBackendDto.java index 3d5f05efb..ed9366bf7 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEBackendDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEBackendDto.java @@ -13,14 +13,14 @@ public record VaultJWEBackendDto( @JsonProperty(value = "provider", required = true) - // references vendor in VaultJWEBackendDto (coming from id in StorageProfileDto) + // references id in StorageProfileDto (aka. vendor in client profile) String provider, @JsonProperty(value = "defaultPath", required = true) String defaultPath, @JsonProperty(value = "nickname", required = true) String nickname, @JsonProperty(value = "uuid", required = true) - String uuid, + String uuid, // vault UUID, will be used as bookmark UUID @JsonProperty(value = "region", required = true) String region, diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 99eab308b..f635afbde 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -80,6 +80,7 @@ quarkus.hibernate-orm.database.globally-quoted-identifiers=true quarkus.flyway.migrate-at-start=true quarkus.flyway.locations=classpath:org/cryptomator/hub/flyway %dev.quarkus.flyway.ignore-missing-migrations=true +%dev.quarkus.flyway.validate-migration-naming=true # https://quarkus.io/guides/databases-dev-services # https://stackoverflow.com/questions/44654216/correct-way-to-install-psql-without-full-postgres-on-macos # psql -h localhost -p 54082 -U quarkus -d quarkus diff --git a/backend/src/main/resources/org/cryptomator/hub/flyway/V80__StorageProfile.sql b/backend/src/main/resources/org/cryptomator/hub/flyway/V80__StorageProfile.sql new file mode 100644 index 000000000..83a4fcf17 --- /dev/null +++ b/backend/src/main/resources/org/cryptomator/hub/flyway/V80__StorageProfile.sql @@ -0,0 +1,38 @@ +CREATE TABLE "storage_profile" +( + "id" UUID NOT NULL, + "name" VARCHAR, + + -- (1) bucket creation, template upload and client profile + "scheme" VARCHAR, + "hostname" VARCHAR, + "port" INT4, + "region" VARCHAR, + "regions" text[], + "withPathStyleAccessEnabled" + bool, + + -- (2) bucket creation only (i.e. STS-case) + "bucketPrefix" VARCHAR, + "stsRoleArnClient" VARCHAR, + "stsRoleArnHub" VARCHAR, + "stsEndpoint" VARCHAR, + + + -- (3) client profile + -- (3a) client profile attributes + "protocol" VARCHAR NOT NULL, + "oauthClientId" VARCHAR, + "oauthTokenUrl" VARCHAR, + "oauthAuthorizationUrl" + VARCHAR, + -- (3b) client profile custom properties + "stsRoleArn" VARCHAR, + "stsRoleArn2" VARCHAR, + "stsDurationSeconds" INT4, + "oAuthTokenExchangeAudience" + VARCHAR, + + CONSTRAINT "STORAGE_PROFILE_PK" PRIMARY KEY ("id") + -- TODO https://github.com/shift7-ch/cipherduck-hub/issues/4 (R3) regions in region contraint? +); \ No newline at end of file diff --git a/frontend/src/components/CreateVault.vue b/frontend/src/components/CreateVault.vue index ced886f8f..342b5f1bc 100644 --- a/frontend/src/components/CreateVault.vue +++ b/frontend/src/components/CreateVault.vue @@ -689,7 +689,7 @@ async function uploadVaultTemplate() { const client = new S3Client({ region: selectedRegion.value, endpoint: endpoint, - forcePathStyle: true, + forcePathStyle: selectedBackend.value.withPathStyleAccessEnabled, credentials:{ accessKeyId: vaultAccessKeyId.value, secretAccessKey: vaultSecretKey.value