Skip to content

Commit b575a75

Browse files
authored
Merge pull request #315 from cryptomator/feature/fix-legacy-hub-compatibility
Fix legacy hub compatibility
2 parents d52b272 + fb5c91f commit b575a75

File tree

7 files changed

+130
-51
lines changed

7 files changed

+130
-51
lines changed
Lines changed: 101 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.cryptomator.hub.api;
22

3+
import com.fasterxml.jackson.annotation.JsonCreator;
34
import com.fasterxml.jackson.annotation.JsonProperty;
45
import jakarta.annotation.Nullable;
56
import org.cryptomator.hub.entities.User;
@@ -10,45 +11,120 @@
1011

1112
public final class UserDto extends AuthorityDto {
1213

14+
private final String email;
15+
private final String language;
16+
private final Set<DeviceResource.DeviceDto> devices;
17+
private final String ecdhPublicKey;
18+
private final String ecdsaPublicKey;
19+
private final String privateKeys;
20+
private final String setupCode;
21+
22+
@JsonCreator
23+
public UserDto(
24+
@JsonProperty("id") String id,
25+
@JsonProperty("name") String name,
26+
@JsonProperty("pictureUrl") String pictureUrl,
27+
@JsonProperty("email") String email,
28+
@JsonProperty("language") String language,
29+
@JsonProperty("devices") Set<DeviceResource.DeviceDto> devices,
30+
// Accept either "ecdhPublicKey" or the legacy "publicKey" on input
31+
@Nullable @JsonProperty("ecdhPublicKey") @OnlyBase64Chars String ecdhPublicKey,
32+
@Nullable @JsonProperty("publicKey") @OnlyBase64Chars String publicKey,
33+
@Nullable @JsonProperty("ecdsaPublicKey") @OnlyBase64Chars String ecdsaPublicKey,
34+
// Accept either "privateKeys" or the legacy "privateKey" on input
35+
@Nullable @JsonProperty("privateKeys") @ValidJWE String privateKeys,
36+
@Nullable @JsonProperty("privateKey") @ValidJWE String privateKey,
37+
@Nullable @JsonProperty("setupCode") @ValidJWE String setupCode) {
38+
super(id, Type.USER, name, pictureUrl);
39+
this.email = email;
40+
this.language = language;
41+
this.devices = devices;
42+
this.ecdhPublicKey = ecdhPublicKey != null ? ecdhPublicKey : publicKey;
43+
this.ecdsaPublicKey = ecdsaPublicKey;
44+
this.privateKeys = privateKeys != null ? privateKeys : privateKey;
45+
this.setupCode = setupCode;
46+
}
47+
48+
public UserDto(
49+
String id,
50+
String name,
51+
String pictureUrl,
52+
String email,
53+
String language,
54+
Set<DeviceResource.DeviceDto> devices,
55+
String ecdhPublicKey,
56+
String ecdsaPublicKey,
57+
String privateKeys,
58+
String setupCode) {
59+
this(id, name, pictureUrl, email, language, devices, ecdhPublicKey, ecdhPublicKey, ecdsaPublicKey, privateKeys, privateKeys, setupCode);
60+
}
61+
1362
@JsonProperty("email")
14-
public final String email;
63+
public String getEmail() {
64+
return email;
65+
}
66+
1567
@JsonProperty("language")
16-
public final String language;
68+
public String getLanguage() {
69+
return language;
70+
}
71+
1772
@JsonProperty("devices")
18-
public final Set<DeviceResource.DeviceDto> devices;
73+
public Set<DeviceResource.DeviceDto> getDevices() {
74+
return devices;
75+
}
76+
1977
@JsonProperty("ecdhPublicKey")
20-
public final String ecdhPublicKey;
21-
@JsonProperty("ecdsaPublicKey")
22-
public final String ecdsaPublicKey;
23-
@JsonProperty("privateKey") // singular name for history reasons (don't break client compatibility)
24-
public final String privateKeys;
25-
@JsonProperty("setupCode")
26-
public final String setupCode;
78+
public String getEcdhPublicKey() {
79+
return ecdhPublicKey;
80+
}
2781

2882
/**
2983
* Same as {@link #ecdhPublicKey}, kept for compatibility purposes
30-
* @deprecated to be removed when all clients moved to the new DTO field names
84+
* @deprecated to be removed in Hub 2.0.0, tracked in <a href="https://github.com/cryptomator/hub/issues/316">#316</a>
3185
*/
3286
@Deprecated(forRemoval = true)
3387
@JsonProperty("publicKey")
34-
public final String legacyEcdhPublicKey;
88+
public String getPublicKey() {
89+
return ecdhPublicKey;
90+
}
3591

36-
UserDto(@JsonProperty("id") String id, @JsonProperty("name") String name, @JsonProperty("pictureUrl") String pictureUrl, @JsonProperty("email") String email, @JsonProperty("language") String language, @JsonProperty("devices") Set<DeviceResource.DeviceDto> devices,
37-
@Nullable @JsonProperty("ecdhPublicKey") @OnlyBase64Chars String ecdhPublicKey, @Nullable @JsonProperty("ecdsaPublicKey") @OnlyBase64Chars String ecdsaPublicKey, @Nullable @JsonProperty("privateKeys") @ValidJWE String privateKeys, @Nullable @JsonProperty("setupCode") @ValidJWE String setupCode) {
38-
super(id, Type.USER, name, pictureUrl);
39-
this.email = email;
40-
this.language = language;
41-
this.devices = devices;
42-
this.ecdhPublicKey = ecdhPublicKey;
43-
this.ecdsaPublicKey = ecdsaPublicKey;
44-
this.privateKeys = privateKeys;
45-
this.setupCode = setupCode;
92+
@JsonProperty("ecdsaPublicKey")
93+
public String getEcdsaPublicKey() {
94+
return ecdsaPublicKey;
95+
}
96+
97+
@JsonProperty("privateKeys")
98+
public String getPrivateKeys() {
99+
return privateKeys;
100+
}
46101

47-
// duplicate fields to maintain backwards compatibility:
48-
this.legacyEcdhPublicKey = ecdhPublicKey;
102+
/**
103+
* Same as {@link #privateKeys}, kept for compatibility purposes
104+
* @deprecated to be removed in Hub 2.0.0, tracked in <a href="https://github.com/cryptomator/hub/issues/316">#316</a>
105+
*/
106+
@Deprecated(forRemoval = true)
107+
@JsonProperty("privateKey")
108+
public String getPrivateKey() {
109+
return privateKeys;
110+
}
111+
112+
@JsonProperty("setupCode")
113+
public String getSetupCode() {
114+
return setupCode;
49115
}
50116

51117
public static UserDto justPublicInfo(User user) {
52-
return new UserDto(user.getId(), user.getName(), user.getPictureUrl(), user.getEmail(), user.getLanguage(), Set.of(), user.getEcdhPublicKey(), user.getEcdsaPublicKey(),null, null);
118+
return new UserDto(
119+
user.getId(),
120+
user.getName(),
121+
user.getPictureUrl(),
122+
user.getEmail(),
123+
user.getLanguage(),
124+
Set.of(),
125+
user.getEcdhPublicKey(),
126+
user.getEcdsaPublicKey(),
127+
null,
128+
null);
53129
}
54130
}

backend/src/main/java/org/cryptomator/hub/api/UsersResource.java

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,18 @@ public Response putMe(@Nullable @Valid UserDto dto) {
8080
user.setPictureUrl(jwt.getClaim("picture"));
8181
user.setEmail(jwt.getClaim("email"));
8282
if (dto != null) {
83-
if (!Objects.equals(user.getSetupCode(), dto.setupCode)) {
84-
user.setSetupCode(dto.setupCode);
83+
if (!Objects.equals(user.getSetupCode(), dto.getSetupCode())) {
84+
user.setSetupCode(dto.getSetupCode());
8585
eventLogger.logUserSetupCodeChanged(jwt.getSubject());
8686
}
87-
if (!Objects.equals(user.getEcdhPublicKey(), dto.ecdhPublicKey) || !Objects.equals(user.getEcdsaPublicKey(), dto.ecdsaPublicKey) || !Objects.equals(user.getPrivateKeys(), dto.privateKeys)) {
88-
user.setEcdhPublicKey(dto.ecdhPublicKey);
89-
user.setEcdsaPublicKey(dto.ecdsaPublicKey);
90-
user.setPrivateKeys(dto.privateKeys);
87+
if (!Objects.equals(user.getEcdhPublicKey(), dto.getEcdhPublicKey()) || !Objects.equals(user.getEcdsaPublicKey(), dto.getEcdsaPublicKey()) || !Objects.equals(user.getPrivateKeys(), dto.getPrivateKeys())) {
88+
user.setEcdhPublicKey(dto.getEcdhPublicKey());
89+
user.setEcdsaPublicKey(dto.getEcdsaPublicKey());
90+
user.setPrivateKeys(dto.getPrivateKeys());
9191
eventLogger.logUserKeysChanged(jwt.getSubject(), jwt.getName());
9292
}
9393
updateDevices(user, dto);
94-
user.setLanguage(dto.language);
94+
user.setLanguage(dto.getLanguage());
9595
}
9696
userRepo.persist(user);
9797
return Response.created(URI.create(".")).build();
@@ -104,18 +104,20 @@ public Response putMe(@Nullable @Valid UserDto dto) {
104104
* @param userDto The DTO
105105
*/
106106
private void updateDevices(User userEntity, UserDto userDto) {
107-
var devices = userEntity.devices.stream().collect(Collectors.toUnmodifiableMap(Device::getId, Function.identity()));
108-
var updatedDevices = userDto.devices.stream()
109-
.filter(d -> devices.containsKey(d.id())) // only look at DTOs for which we find a matching existing entity
110-
.map(dto -> {
111-
var device = devices.get(dto.id());
112-
device.setType(dto.type());
113-
device.setName(dto.name());
114-
device.setPublickey(dto.publicKey());
115-
device.setUserPrivateKeys(dto.userPrivateKeys());
116-
return device;
117-
});
118-
deviceRepo.persist(updatedDevices);
107+
if (userDto.getDevices() != null) {
108+
var devices = userEntity.devices.stream().collect(Collectors.toUnmodifiableMap(Device::getId, Function.identity()));
109+
var updatedDevices = userDto.getDevices().stream()
110+
.filter(d -> devices.containsKey(d.id())) // only look at DTOs for which we find a matching existing entity
111+
.map(dto -> {
112+
var device = devices.get(dto.id());
113+
device.setType(dto.type());
114+
device.setName(dto.name());
115+
device.setPublickey(dto.publicKey());
116+
device.setUserPrivateKeys(dto.userPrivateKeys());
117+
return device;
118+
});
119+
deviceRepo.persist(updatedDevices);
120+
}
119121
}
120122

121123
@POST

backend/src/main/resources/dev-realm.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@
145145
"enabled": true,
146146
"serviceAccountClientId": "cryptomatorhub-cli",
147147
"realmRoles": [
148-
"user"
148+
"user",
149+
"create-vaults"
149150
],
150151
"clientRoles" : {
151152
"realm-management" : [ "manage-users", "view-users" ]

frontend/src/common/backend.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export type UserDto = {
7575
accessibleVaults: VaultDto[];
7676
ecdhPublicKey?: string;
7777
ecdsaPublicKey?: string;
78-
privateKey?: string;
78+
privateKeys?: string;
7979
setupCode?: string;
8080
}
8181

frontend/src/common/userdata.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,10 @@ class UserData {
9393
*/
9494
public async decryptUserKeysWithSetupCode(setupCode: string): Promise<UserKeys> {
9595
const me = await this.me;
96-
if (!me.privateKey) {
96+
if (!me.privateKeys) {
9797
throw new Error('User not initialized.');
9898
}
99-
const userKeys = await UserKeys.recover(me.privateKey, setupCode, await this.ecdhPublicKey, await this.ecdsaPublicKey);
99+
const userKeys = await UserKeys.recover(me.privateKeys, setupCode, await this.ecdhPublicKey, await this.ecdsaPublicKey);
100100
await this.addEcdsaKeyIfMissing(userKeys);
101101
return userKeys;
102102
}
@@ -128,7 +128,7 @@ class UserData {
128128
if (me.setupCode && !me.ecdsaPublicKey) {
129129
const payload: { setupCode: string } = await JWEParser.parse(me.setupCode).decryptEcdhEs(userKeys.ecdhKeyPair.privateKey);
130130
me.ecdsaPublicKey = await userKeys.encodedEcdsaPublicKey();
131-
me.privateKey = await userKeys.encryptWithSetupCode(payload.setupCode);
131+
me.privateKeys = await userKeys.encryptWithSetupCode(payload.setupCode);
132132
for (const device of me.devices) {
133133
device.userPrivateKey = await userKeys.encryptForDevice(base64.parse(device.publicKey));
134134
}

frontend/src/components/InitialSetup.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ async function createUserKey() {
263263
const userKeys = await UserKeys.create();
264264
me.ecdhPublicKey = await userKeys.encodedEcdhPublicKey();
265265
me.ecdsaPublicKey = await userKeys.encodedEcdsaPublicKey();
266-
me.privateKey = await userKeys.encryptWithSetupCode(setupCode.value);
266+
me.privateKeys = await userKeys.encryptWithSetupCode(setupCode.value);
267267
me.setupCode = await JWEBuilder.ecdhEs(userKeys.ecdhKeyPair.publicKey).encrypt({ setupCode: setupCode.value });
268268
const browserKeys = await userdata.createBrowserKeys();
269269
await submitBrowserKeys(browserKeys, me, userKeys);

frontend/src/components/RegenerateSetupCodeDialog.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ async function regenerateSetupCode() {
153153
const me = await userdata.me;
154154
const newCode = crypto.randomUUID();
155155
const userKeys = await userdata.decryptUserKeysWithBrowser();
156-
me.privateKey = await userKeys.encryptWithSetupCode(newCode);
156+
me.privateKeys = await userKeys.encryptWithSetupCode(newCode);
157157
me.setupCode = await JWEBuilder.ecdhEs(userKeys.ecdhKeyPair.publicKey).encrypt({ setupCode: newCode });
158158
await backend.users.putMe(me);
159159
setupCode.value = newCode;

0 commit comments

Comments
 (0)