Skip to content

Commit

Permalink
Add vault metadata WiP (#19).
Browse files Browse the repository at this point in the history
  • Loading branch information
chenkins committed Feb 28, 2024
1 parent 605215b commit 4fe69d2
Show file tree
Hide file tree
Showing 24 changed files with 332 additions and 101 deletions.
13 changes: 12 additions & 1 deletion backend/src/main/java/org/cryptomator/hub/api/VaultResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,10 @@ public Response createOrUpdate(@PathParam("vaultId") UUID vaultId, @Valid @NotNu
vault.description = vaultDto.description;
vault.archived = existingVault.isEmpty() ? false : vaultDto.archived;

// / start cipherduck extension
vault.metadata = vaultDto.metadata;
// \ end cipherduck extension

vault.persistAndFlush(); // trigger PersistenceException before we continue with
if (existingVault.isEmpty()) {
var access = new VaultAccess();
Expand Down Expand Up @@ -525,10 +529,17 @@ public record VaultDto(@JsonProperty("id") UUID id,
@JsonProperty("masterkey") @OnlyBase64Chars String masterkey, @JsonProperty("iterations") Integer iterations,
@JsonProperty("salt") @OnlyBase64Chars String salt,
@JsonProperty("authPublicKey") @OnlyBase64Chars String authPublicKey, @JsonProperty("authPrivateKey") @OnlyBase64Chars String authPrivateKey
// / start cipherduck extension
,@JsonProperty("metadata") @NotNull String metadata
// \ end cipherduck extension
) {

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.id, entity.name, entity.description, entity.archived, entity.creationTime.truncatedTo(ChronoUnit.MILLIS), entity.masterkey, entity.iterations, entity.salt, entity.authenticationPublicKey, entity.authenticationPrivateKey
// / start cipherduck extension
, entity.metadata
// \ end cipherduck extension
);
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.cryptomator.hub.api.cipherduck;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.UUID;

public record CreateS3STSBucketDto(
@JsonProperty("vaultId")
String vaultId,
@JsonProperty("storageConfigId")
UUID storageConfigId,
@JsonProperty("vaultConfigToken")
String vaultConfigToken,
@JsonProperty("rootDirHash")
String rootDirHash,
@JsonProperty("awsAccessKey")
String awsAccessKey,
@JsonProperty("awsSecretKey")
String awsSecretKey,
@JsonProperty("sessionToken")
String sessionToken,
@JsonProperty("region")
String region
) {

}

Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Path("/storageprofile")
public class StorageProfileResource {
Expand Down Expand Up @@ -137,7 +136,7 @@ public Response archive(@PathParam("profileId") UUID profileId, @FormParam("arch
@Transactional
@Operation(summary = "get configs for storage backends", description = "get list of configs for storage backends")
@APIResponse(responseCode = "200", description = "uploaded storage configuration")
public VaultJWEPayloadDto getVaultJWEBackendDto(final StorageProfileDto.Protocol protocol) {
public VaultMasterkeyJWEDto getVaultJWEBackendDto(final StorageProfileDto.Protocol protocol) {
// N.B. temporary workaround to have VaultJWEBackendDto exposed in openapi.json for now....
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public enum S3_STORAGE_CLASSES {
@Schema(description = "Whether to use path style for S3 endpoint for template upload/bucket creation.", example = "false", defaultValue = "false")
Boolean withPathStyleAccessEnabled = false;

@JsonProperty(value = "storageClass")
@JsonProperty(value = "storageClass", defaultValue = "STANDARD")
@Schema(description = "Storage class for upload. Defaults to STANDARD", example = "STANDARD", required = true)
S3_STORAGE_CLASSES storageClass = S3_STORAGE_CLASSES.STANDARD;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ public enum S3_SERVERSIDE_ENCRYPTION {
String stsRoleArnClient;

@JsonProperty(value = "stsRoleArnHub", required = true)
@Schema(description = "STS role for frontend to assume to create buckets (used with inline policy and passed to hub backend). Will be the same as stsRoleArnClient for AWS, different for MinIO.", example = "arn:aws:iam::<ACCOUNT ID>:role/cipherduck-createbucket")
@Schema(description = "STS role for frontend to assume to create buckets (used with inline policy and passed to hub storage). Will be the same as stsRoleArnClient for AWS, different for MinIO.", example = "arn:aws:iam::<ACCOUNT ID>:role/cipherduck-createbucket")
String stsRoleArnHub;

@JsonProperty("stsEndpoint")
@Schema(description = "STS endpoint to use for AssumeRoleWithWebIdentity and AssumeRole for getting a temporary access token passed to the backend. Defaults to AWS SDK default.", nullable = true)
@Schema(description = "STS endpoint to use for AssumeRoleWithWebIdentity and AssumeRole for getting a temporary access token passed to the storage. Defaults to AWS SDK default.", nullable = true)
String stsEndpoint;

@JsonProperty(value = "bucketVersioning", defaultValue = "true", required = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class StorageResource {
@APIResponse(responseCode = "400", description = "Could not create bucket")
@APIResponse(responseCode = "409", description = "Vault with this ID or bucket with this name already exists")
@APIResponse(responseCode = "410", description = "Storage profile is archived")
public Response createBucket(@PathParam("vaultId") UUID vaultId, final StorageDto storage) {
public Response createBucket(@PathParam("vaultId") UUID vaultId, final CreateS3STSBucketDto storage) {
Optional<Vault> vault = Vault.<Vault>findByIdOptional(vaultId);
if (vault.isPresent()) {
throw new ClientErrorException(String.format("Vault with ID %s already exists", vaultId), Response.Status.CONFLICT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/**
* Part of vault JWE specifying the vault bookmark.
* Allows to create a bookmark in the client referencing the vendor in the storage profiles.
* This Java record is unused in hub, only its ts counterpart in `backend.ts`.
* This Java record is unused in hub, only its ts counterpart in `storage.ts`.
* It will used in Cipherduck client in the OpenAPI generator.
*/
public record VaultJWEBackendDto(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public record VaultJWEPayloadDto(
// masterkey
String key,

@JsonProperty(value = "backend", required = true)
@JsonProperty(value = "storage", required = true)
VaultJWEBackendDto backend,

@JsonProperty(value = "automaticAccessGrant", required = true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.cryptomator.hub.api.cipherduck;

import com.fasterxml.jackson.annotation.JsonProperty;

public record VaultMasterkeyJWEDto(
@JsonProperty(value = "key", required = true)
// masterkey
String key
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.cryptomator.hub.api.cipherduck;

import com.fasterxml.jackson.annotation.JsonProperty;

public record VaultMetadataJWEAutomaticAccessGrantDto(
@JsonProperty(value = "enabled", defaultValue = "true")
boolean enabled,

// where -1 means "grant to anyone", where 0, 1, 2 would be the number of edges between any vault owner and the grantee. Exact algorithm tbd
@JsonProperty(value = "maxWotDepth", defaultValue = "-1")
int maxWotDepth) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.cryptomator.hub.api.cipherduck;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Map;

public record VaultMetadataJWEDto(
@JsonProperty(value = "fileFormat", required = true)
String fileFormat,
@JsonProperty(value = "nameFormat", required = true)
String nameFormat,

@JsonProperty(value = "keys", required = true)
Map<String, String> keys,

@JsonProperty(value = "latestFileKey", required = true)
String latestFileKey,

@JsonProperty(value = "nameKey", required = true)
String nameKey,

@JsonProperty(value = "kdf", required = true)
String kdf,

@JsonProperty(value = "com.cipherduck.storage", required = true)
VaultMetadataJWEStorageDto storage,

@JsonProperty(value = "org.cryptomator.automaticAccessGrant", required = true)
VaultMetadataJWEAutomaticAccessGrantDto automaticAccessGrant
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.cryptomator.hub.api.cipherduck;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
* Part of vault JWE specifying the vault metadata.
* Allows to create a bookmark in the client referencing the vendor in the storage profiles.
* This Java record is unused in hub, only its ts counterpart in `storage.ts`.
* Cipherduck client uses code generated by the OpenAPI generator.
*/
public record VaultMetadataJWEStorageDto(
@JsonProperty(value = "provider", required = true)
// 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 = "region", required = true)
String region,

@JsonProperty(value = "username")
// for non-STS
String username,
@JsonProperty(value = "password")
// for non-STS
String password
) {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.cryptomator.hub.api.cipherduck.storage;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicSessionCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
Expand All @@ -23,7 +22,7 @@
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.core.Response;
import org.apache.commons.io.IOUtils;
import org.cryptomator.hub.api.cipherduck.StorageDto;
import org.cryptomator.hub.api.cipherduck.CreateS3STSBucketDto;
import org.cryptomator.hub.api.cipherduck.StorageProfileS3STSDto;
import org.jboss.logging.Logger;

Expand All @@ -36,7 +35,7 @@ public class S3StorageHelper {

public static void makeS3Bucket(
final StorageProfileS3STSDto storageConfig,
final StorageDto dto
final CreateS3STSBucketDto dto
) {

if (log.isInfoEnabled()) {
Expand Down
5 changes: 5 additions & 0 deletions backend/src/main/java/org/cryptomator/hub/entities/Vault.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ public class Vault extends PanacheEntityBase {
@Column(name = "archived", nullable = false)
public boolean archived;

// / start cipherduck extension
@Column(name = "metadata", nullable = false)
public String metadata;
// \ end cipherduck extension

public Optional<ECPublicKey> getAuthenticationPublicKey() {
if (authenticationPublicKey == null) {
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
package org.cryptomator.hub.entities.cipherduck;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import org.cryptomator.hub.api.cipherduck.StorageProfileS3STSDto;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

import java.util.List;
import java.util.UUID;

@Entity
@Table(name = "storage_profile_s3_sts")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ CREATE TABLE "vault"
"masterkey" VARCHAR(255), -- deprecated ("vault admin password")
"auth_pubkey" VARCHAR, -- deprecated ("vault admin password")
"auth_prvkey" VARCHAR, -- deprecated ("vault admin password")
-- / start cipherduck extension
"metadata" VARCHAR NOT NULL UNIQUE, -- encrypted using vault masterkey (JWE ECDH-ES)
-- \ end cipherduck extension
CONSTRAINT "VAULT_PK" PRIMARY KEY ("id")
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,8 @@
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.comparesEqualTo;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.text.IsEqualIgnoringCase.equalToIgnoringCase;

@QuarkusTest
Expand Down Expand Up @@ -83,9 +82,17 @@ public class TestVaultDtoValidation {
private static final String VALID_AUTH_PUB = "base64";
private static final String VALID_AUTH_PRI = "base64";

// / start cipherduck extension
private static final String VALID_METDATA = "base64";
// \ end cipherduck extension

@Test
public void testValidDto() {
var dto = new VaultResource.VaultDto(VALID_ID, VALID_NAME, "foobarbaz", false, Instant.parse("2020-02-20T20:20:20Z"), VALID_MASTERKEY, 8, VALID_SALT, VALID_AUTH_PUB, VALID_AUTH_PRI);
var dto = new VaultResource.VaultDto(VALID_ID, VALID_NAME, "foobarbaz", false, Instant.parse("2020-02-20T20:20:20Z"), VALID_MASTERKEY, 8, VALID_SALT, VALID_AUTH_PUB
// / start cipherduck extension
, VALID_AUTH_PRI, VALID_METDATA
// \ end cipherduck extension
);
var violations = validator.validate(dto);
MatcherAssert.assertThat(violations, Matchers.empty());
}
Expand Down Expand Up @@ -193,7 +200,8 @@ public void testUnlock2() {
}

@Test
@DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/keys/noSuchDevice returns 403") // legacy unlock must not encourage to register a legacy device by responding with 404 here
@DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/keys/noSuchDevice returns 403")
// legacy unlock must not encourage to register a legacy device by responding with 404 here
public void testUnlock3() {
when().get("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100001111", "noSuchDevice")
.then().statusCode(403);
Expand Down Expand Up @@ -247,7 +255,11 @@ public class CreateVaults {
@DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100003333 returns 201")
public void testCreateVault1() {
var uuid = UUID.fromString("7E57C0DE-0000-4000-8000-000100003333");
var vaultDto = new VaultResource.VaultDto(uuid, "My Vault", "Test vault 3", false, Instant.parse("2112-12-21T21:12:21Z"), "masterkey3", 42, "NaCl", "authPubKey3", "authPrvKey3");
var vaultDto = new VaultResource.VaultDto(uuid, "My Vault", "Test vault 3", false, Instant.parse("2112-12-21T21:12:21Z"), "masterkey3", 42, "NaCl", "authPubKey3", "authPrvKey3"
// / start cipherduck extension
, "metadata"
// \ end cipherduck extension
);

given().contentType(ContentType.JSON).body(vaultDto)
.when().put("/vaults/{vaultId}", "7E57C0DE-0000-4000-8000-000100003333")
Expand All @@ -272,7 +284,11 @@ public void testCreateVault2() {
@DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100004444 returns 201 ignoring archived flag")
public void testCreateVault3() {
var uuid = UUID.fromString("7E57C0DE-0000-4000-8000-000100004444");
var vaultDto = new VaultResource.VaultDto(uuid, "My Vault", "Test vault 4", true, Instant.parse("2112-12-21T21:12:21Z"), "masterkey4", 42, "NaCl", "authPubKey4", "authPrvKey4");
var vaultDto = new VaultResource.VaultDto(uuid, "My Vault", "Test vault 4", true, Instant.parse("2112-12-21T21:12:21Z"), "masterkey4", 42, "NaCl", "authPubKey4", "authPrvKey4"
// / start cipherduck extension
, "metadata"
// \ end cipherduck extension
);

given().contentType(ContentType.JSON).body(vaultDto)
.when().put("/vaults/{vaultId}", "7E57C0DE-0000-4000-8000-000100004444")
Expand All @@ -288,7 +304,11 @@ public void testCreateVault3() {
@DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100003333 returns 200, updating only name, description and archive flag")
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");
var vaultDto = new VaultResource.VaultDto(uuid, "VaultUpdated", "Vault updated.", true, Instant.parse("2222-11-11T11:11:11Z"), "doNotUpdate", 27, "doNotUpdate", "doNotUpdate", "doNotUpdate"
// / start cipherduck extension
, "metadata"
// \ end cipherduck extension
);
given().contentType(ContentType.JSON)
.body(vaultDto)
.when().put("/vaults/{vaultId}", "7E57C0DE-0000-4000-8000-000100003333")
Expand Down Expand Up @@ -752,7 +772,11 @@ public void testCreateVaultExceedingSeats() {
//Assumptions.assumeTrue(EffectiveVaultAccess.countEffectiveVaultUsers() > 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");
var vaultDto = new VaultResource.VaultDto(uuid, "My Vault", "Test vault 4", false, Instant.parse("2112-12-21T21:12:21Z"), "masterkey3", 42, "NaCl", "authPubKey3", "authPrvKey3"
// / start cipherduck extension
, "metadata"
// \ end cipherduck extension
);
given().contentType(ContentType.JSON).body(vaultDto)
.when().put("/vaults/{vaultId}", "7E57C0DE-0000-4000-8000-0001FFFF3333")
.then().statusCode(402);
Expand All @@ -765,7 +789,11 @@ public void testCreateVaultNotExceedingSeats() {
//Assumptions.assumeTrue(EffectiveVaultAccess.countEffectiveVaultUsers() > 5);

var uuid = UUID.fromString("7E57C0DE-0000-4000-8000-0001FFFF3333");
var vaultDto = new VaultResource.VaultDto(uuid, "My Vault", "Test vault 3", false, Instant.parse("2112-12-21T21:12:21Z"), "masterkey3", 42, "NaCl", "authPubKey3", "authPrvKey3");
var vaultDto = new VaultResource.VaultDto(uuid, "My Vault", "Test vault 3", false, Instant.parse("2112-12-21T21:12:21Z"), "masterkey3", 42, "NaCl", "authPubKey3", "authPrvKey3"
// / start cipherduck extension
, "metadata"
// \ end cipherduck extension
);
given().contentType(ContentType.JSON).body(vaultDto)
.when().put("/vaults/{vaultId}", "7E57C0DE-0000-4000-8000-0001FFFF3333")
.then().statusCode(201)
Expand All @@ -782,7 +810,11 @@ public void testUpdateVaultDespiteLicenseExceeded() {
//Assumptions.assumeTrue(EffectiveVaultAccess.countEffectiveVaultUsers() > 5);

var uuid = UUID.fromString("7E57C0DE-0000-4000-8000-0001FFFF3333");
var vaultDto = new VaultResource.VaultDto(uuid, "VaultUpdated", "Vault updated.", true, Instant.parse("2222-11-11T11:11:11Z"), "someVaule", -1, "doNotUpdate", "doNotUpdate", "doNotUpdate");
var vaultDto = new VaultResource.VaultDto(uuid, "VaultUpdated", "Vault updated.", true, Instant.parse("2222-11-11T11:11:11Z"), "someVaule", -1, "doNotUpdate", "doNotUpdate", "doNotUpdate"
// / start cipherduck extension
, "metadata"
// \ end cipherduck extension
);
given().contentType(ContentType.JSON)
.body(vaultDto)
.when().put("/vaults/{vaultId}", "7E57C0DE-0000-4000-8000-0001FFFF3333")
Expand Down
Loading

0 comments on commit 4fe69d2

Please sign in to comment.