Skip to content

Commit

Permalink
Merge branch 'develop' into feature/uvf
Browse files Browse the repository at this point in the history
# Conflicts:
#	backend/src/main/resources/org/cryptomator/hub/flyway/ERM.png
#	frontend/src/common/jwt.ts
#	frontend/src/components/GrantPermissionDialog.vue
#	frontend/src/components/VaultDetails.vue
#	frontend/test/common/crypto.spec.ts
  • Loading branch information
overheadhunter committed Jul 12, 2024
2 parents 26369a5 + b0d2066 commit e77ca7a
Show file tree
Hide file tree
Showing 43 changed files with 1,614 additions and 204 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ <T extends Authority> void syncAddedAuthorities(Map<String, T> keycloakAuthoriti
<T extends Authority> Set<String> syncDeletedAuthorities(Map<String, T> keycloakAuthorities, Map<String, T> databaseAuthorities) {
var deletedAuthorities = diff(databaseAuthorities.keySet(), keycloakAuthorities.keySet());
for (var id : deletedAuthorities) {
authorityRepo.delete(keycloakAuthorities.get(id));
authorityRepo.delete(databaseAuthorities.get(id));
}
return deletedAuthorities;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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.SignedWotIdEvent;
import org.cryptomator.hub.entities.events.VaultAccessGrantedEvent;
import org.cryptomator.hub.entities.events.VaultCreatedEvent;
import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent;
Expand Down Expand Up @@ -80,6 +81,7 @@ public List<AuditEventDto> getAllEvents(@QueryParam("startDate") Instant startDa
@JsonSubTypes({ //
@JsonSubTypes.Type(value = DeviceRegisteredEventDto.class, name = DeviceRegisteredEvent.TYPE), //
@JsonSubTypes.Type(value = DeviceRemovedEventDto.class, name = DeviceRemovedEvent.TYPE), //
@JsonSubTypes.Type(value = SignedWotIdEvent.class, name = SignedWotIdEvent.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), //
Expand All @@ -101,6 +103,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 SignedWotIdEvent evt -> new SignedWotIdEventDto(evt.getId(), evt.getTimestamp(), SignedWotIdEvent.TYPE, evt.getUserId(), evt.getSignerId(), evt.getSignerKey(), evt.getSignature());
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.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());
Expand All @@ -121,6 +124,9 @@ record DeviceRegisteredEventDto(long id, Instant timestamp, String type, @JsonPr
record DeviceRemovedEventDto(long id, Instant timestamp, String type, @JsonProperty("removedBy") String removedBy, @JsonProperty("deviceId") String deviceId) implements AuditEventDto {
}

record SignedWotIdEventDto(long id, Instant timestamp, String type, @JsonProperty("userId") String userId, @JsonProperty("signerId") String signerId, @JsonProperty("signerKey") String signerKey, @JsonProperty("signature") String signature) 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 {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ public static MemberDto fromEntity(Group group, VaultAccess.Role role) {
return new MemberDto(group.getId(), Type.GROUP, group.getName(), null, null, null, role);
}

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

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
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.cryptomator.hub.entities.Settings;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;

@Path("/settings")
public class SettingsResource {

@Inject
Settings.Repository settingsRepo;

@GET
@RolesAllowed("user")
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "get the billing information")
@APIResponse(responseCode = "200")
@Transactional
public SettingsDto get() {
return SettingsDto.fromEntity(settingsRepo.get());
}

@PUT
@RolesAllowed("admin")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(summary = "update settings")
@APIResponse(responseCode = "204", description = "token set")
@APIResponse(responseCode = "400", description = "invalid settings")
@APIResponse(responseCode = "403", description = "only admins are allowed to update settings")
@Transactional
public Response put(@NotNull @Valid SettingsDto dto) {
var settings = settingsRepo.get();
settings.setWotMaxDepth(dto.wotMaxDepth);
settings.setWotIdVerifyLen(dto.wotIdVerifyLen);
settingsRepo.persist(settings);
return Response.status(Response.Status.NO_CONTENT).build();
}

public record SettingsDto(@JsonProperty("hubId") String hubId, @JsonProperty("wotMaxDepth") @Min(0) @Max(9) int wotMaxDepth, @JsonProperty("wotIdVerifyLen") @Min(0) int wotIdVerifyLen) {

public static SettingsDto fromEntity(Settings entity) {
return new SettingsDto(entity.getHubId(), entity.getWotMaxDepth(), entity.getWotIdVerifyLen());
}

}

}
6 changes: 5 additions & 1 deletion backend/src/main/java/org/cryptomator/hub/api/UserDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ public final class UserDto extends AuthorityDto {
@JsonProperty("setupCode")
public final String setupCode;

@Deprecated
/**
* Same as {@link #ecdhPublicKey}, kept for compatibility purposes
* @deprecated to be removed when all clients moved to the new DTO field names
*/
@Deprecated(forRemoval = true)
@JsonProperty("publicKey")
public final String legacyEcdhPublicKey;

Expand Down
67 changes: 66 additions & 1 deletion backend/src/main/java/org/cryptomator/hub/api/UsersResource.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.cryptomator.hub.api;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.annotation.Nullable;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
Expand All @@ -8,17 +9,21 @@
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.cryptomator.hub.entities.AccessToken;
import org.cryptomator.hub.entities.Device;
import org.cryptomator.hub.entities.EffectiveWot;
import org.cryptomator.hub.entities.User;
import org.cryptomator.hub.entities.Vault;
import org.cryptomator.hub.entities.WotEntry;
import org.cryptomator.hub.entities.events.EventLogger;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.eclipse.microprofile.openapi.annotations.Operation;
Expand Down Expand Up @@ -48,6 +53,10 @@ public class UsersResource {
Device.Repository deviceRepo;
@Inject
Vault.Repository vaultRepo;
@Inject
WotEntry.Repository wotRepo;
@Inject
EffectiveWot.Repository effectiveWotRepo;

@Inject
JsonWebToken jwt;
Expand Down Expand Up @@ -168,7 +177,63 @@ public Response resetMe() {
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "list all users")
public List<UserDto> getAll() {
return userRepo.findAll().<User>stream().map(UserDto::justPublicInfo).toList();
return userRepo.findAll().stream().map(UserDto::justPublicInfo).toList();
}

@PUT
@Path("/trusted/{userId}")
@RolesAllowed("user")
@Transactional
@Consumes(MediaType.TEXT_PLAIN)
@Operation(summary = "adds/updates trust", description = "Stores a signature for the given user.")
@APIResponse(responseCode = "204", description = "signature stored")
public Response putSignature(@PathParam("userId") String userId, @NotNull String signature) {
var signer = userRepo.findById(jwt.getSubject());
var id = new WotEntry.Id();
id.setUserId(userId);
id.setSignerId(signer.getId());
var entry = wotRepo.findById(id);
if (entry == null) {
entry = new WotEntry();
entry.setId(id);
}
entry.setSignature(signature);
wotRepo.persist(entry);
eventLogger.logWotIdSigned(userId, signer.getId(), signer.getEcdsaPublicKey(), signature);
return Response.status(Response.Status.NO_CONTENT).build();
}

@GET
@Path("/trusted/{userId}")
@RolesAllowed("user")
@NoCache
@Transactional
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "get trust detail for given user", description = "returns the shortest found signature chain for the given user")
@APIResponse(responseCode = "200")
@APIResponse(responseCode = "404", description = "if no sufficiently short trust chain between the invoking user and the user with the given id has been found")
public TrustedUserDto getTrustedUser(@PathParam("userId") String trustedUserId) {
var trustingUserId = jwt.getSubject();
return effectiveWotRepo.findTrusted(trustingUserId, trustedUserId).singleResultOptional().map(TrustedUserDto::fromEntity).orElseThrow(NotFoundException::new);
}

@GET
@Path("/trusted")
@RolesAllowed("user")
@NoCache
@Transactional
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "get trusted users", description = "returns a list of users trusted by the currently logged-in user")
@APIResponse(responseCode = "200")
public List<TrustedUserDto> getTrustedUsers() {
var trustingUserId = jwt.getSubject();
return effectiveWotRepo.findTrusted(trustingUserId).stream().map(TrustedUserDto::fromEntity).toList();
}

public record TrustedUserDto(@JsonProperty("trustedUserId") String trustedUserId, @JsonProperty("signatureChain") List<String> signatureChain) {

public static TrustedUserDto fromEntity(EffectiveWot entity) {
return new TrustedUserDto(entity.getId().getTrustedUserId(), List.of(entity.getSignatureChain()));
}
}
}
108 changes: 108 additions & 0 deletions backend/src/main/java/org/cryptomator/hub/entities/EffectiveWot.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.cryptomator.hub.entities;

import io.quarkus.hibernate.orm.panache.PanacheQuery;
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
import io.quarkus.panache.common.Parameters;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table;
import org.hibernate.annotations.Immutable;
import org.hibernate.annotations.Type;

import java.io.Serializable;
import java.util.Objects;

@Entity
@Immutable
@Table(name = "effective_wot")
@NamedQuery(name = "EffectiveWot.findTrustedUsers", query = """
SELECT wot
FROM EffectiveWot wot
WHERE wot.id.trustingUserId = :trustingUserId
""")
@NamedQuery(name = "EffectiveWot.findTrustedUser", query = """
SELECT wot
FROM EffectiveWot wot
WHERE wot.id.trustingUserId = :trustingUserId AND wot.id.trustedUserId = :trustedUserId
""")
public class EffectiveWot {

@EmbeddedId
private Id id;

@Column(name = "signature_chain")
@Type(StringArrayType.class)
private String[] signatureChain;

public Id getId() {
return id;
}

public void setId(Id id) {
this.id = id;
}

public String[] getSignatureChain() {
return signatureChain;
}

public void setSignatureChain(String[] signatureChain) {
this.signatureChain = signatureChain;
}

@Embeddable
public static class Id implements Serializable {

@Column(name = "trusting_user_id")
private String trustingUserId;

@Column(name = "trusted_user_id")
private String trustedUserId;

public String getTrustingUserId() {
return trustingUserId;
}

public String getTrustedUserId() {
return trustedUserId;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof Id other) {
return Objects.equals(trustingUserId, other.trustingUserId) //
&& Objects.equals(trustedUserId, other.trustedUserId);
}
return false;
}

@Override
public int hashCode() {
return Objects.hash(trustingUserId, trustedUserId);
}

@Override
public String toString() {
return "EffectiveWotId{" +
"trustingUserId='" + trustingUserId + '\'' +
", trustedUserId='" + trustedUserId + '\'' +
'}';
}
}

@ApplicationScoped
public static class Repository implements PanacheRepositoryBase<EffectiveWot, Id> {
public PanacheQuery<EffectiveWot> findTrusted(String trustingUserId) {
return find("#EffectiveWot.findTrustedUsers", Parameters.with("trustingUserId", trustingUserId));
}

public PanacheQuery<EffectiveWot> findTrusted(String trustingUserId, String trustedUserId) {
return find("#EffectiveWot.findTrustedUser", Parameters.with("trustingUserId", trustingUserId).and("trustedUserId", trustedUserId));
}
}
}
Loading

0 comments on commit e77ca7a

Please sign in to comment.