Skip to content

Commit 4b88459

Browse files
committed
Merge branch 'develop' into feature/recover
2 parents 48e5649 + 6899faa commit 4b88459

39 files changed

+441
-367
lines changed

.github/workflows/build.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
2222
with:
2323
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
24-
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
24+
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
2525
with:
2626
node-version: ${{ env.NODE_VERSION }}
2727
cache: 'npm'
@@ -36,7 +36,7 @@ jobs:
3636
working-directory: frontend
3737
run: npm run dist
3838
- name: SonarCloud Scan Frontend
39-
uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6.0.0
39+
uses: SonarSource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 # v7.0.0
4040
with:
4141
projectBaseDir: frontend
4242
args: >
@@ -49,13 +49,13 @@ jobs:
4949
env:
5050
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
5151
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
52-
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
52+
- uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
5353
with:
5454
distribution: 'temurin'
5555
java-version: ${{ env.JAVA_VERSION }}
5656
cache: 'maven'
5757
- name: Cache SonarCloud packages
58-
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
58+
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
5959
with:
6060
path: ~/.sonar/cache
6161
key: ${{ runner.os }}-sonar
@@ -96,7 +96,7 @@ jobs:
9696
packages: write
9797
steps:
9898
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
99-
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
99+
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
100100
with:
101101
node-version: ${{ env.NODE_VERSION }}
102102
cache: 'npm'

.github/workflows/keycloak.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
packages: write
2626
steps:
2727
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
28-
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
28+
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
2929
with:
3030
node-version: ${{ env.NODE_VERSION }}
3131
cache: 'npm'

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12-
- Show pictures of the groups in the Vaults member list.
12+
- Show pictures of the groups in the Vaults member list. (#375)
1313

1414
### Changed
1515

1616
- Updated Keycloak to 26.4.7
17+
- Update Quarkus to 3.20.4 LTS
18+
- Improved efficiency of keycloak-to-hub data sync (#377)
19+
- Improved efficiency of group-based access permission checks (#372)
20+
- Migrated aes-siv and base encoding libraries to [`@noble/ciphers`](https://github.com/paulmillr/noble-ciphers) and [`@scure/base`](https://github.com/paulmillr/scure-base/) (#373)
21+
22+
### Security
23+
24+
- CVE-2025-64756, CVE-2025-64118: removed `glob` and `tar` dependencies
25+
- CVE-2025-64718, CVE-2025-62522: updated `js-yaml` and `vite`
1726

1827
## [1.4.6](https://github.com/cryptomator/hub/compare/1.4.5...1.4.6)
1928

backend/pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
1111
<project.jdk.version>21</project.jdk.version>
1212
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
13-
<quarkus.platform.version>3.20.3</quarkus.platform.version>
13+
<quarkus.platform.version>3.20.4</quarkus.platform.version>
1414
<jwt.version>4.5.0</jwt.version>
1515
<compiler-plugin.version>3.14.1</compiler-plugin.version>
16-
<dependency-plugin.version>3.8.1</dependency-plugin.version>
16+
<dependency-plugin.version>3.9.0</dependency-plugin.version>
1717
<surefire-plugin.version>3.5.4</surefire-plugin.version>
1818
<failsafe-plugin.version>3.5.4</failsafe-plugin.version>
19-
<junit-tree-reporter.version>1.4.0</junit-tree-reporter.version>
19+
<junit-tree-reporter.version>1.5.1</junit-tree-reporter.version>
2020
</properties>
2121

2222
<dependencyManagement>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ public Response claimOwnership(@PathParam("vaultId") UUID vaultId, @FormParam("p
610610
}
611611

612612
@JsonInclude(JsonInclude.Include.NON_NULL)
613-
public record VaultDto(@JsonProperty("id") UUID id,
613+
public record VaultDto(@JsonProperty("id") @NotNull UUID id,
614614
@JsonProperty("name") @NoHtmlOrScriptChars @NotBlank String name,
615615
@JsonProperty("creationTime") Instant creationTime, @JsonProperty("description") @NoHtmlOrScriptChars String description,
616616
@JsonProperty("archived") boolean archived,

backend/src/main/java/org/cryptomator/hub/entities/Group.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ public Set<Authority> getMembers() {
3232
return members;
3333
}
3434

35-
public void setMembers(Set<Authority> members) {
36-
this.members = members;
37-
}
38-
3935
@Transient
4036
public int getMemberSize() {
4137
return members.size();

backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPuller.java

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.cryptomator.hub.entities.Group;
1010
import org.cryptomator.hub.entities.User;
1111

12+
import java.util.HashMap;
1213
import java.util.HashSet;
1314
import java.util.Map;
1415
import java.util.Set;
@@ -36,18 +37,27 @@ void sync() {
3637

3738
@Transactional
3839
void sync(Map<String, KeycloakGroupDto> keycloakGroups, Map<String, KeycloakUserDto> keycloakUsers) {
40+
// get current state from database:
3941
var databaseUsers = userRepo.findAll().stream().collect(Collectors.toMap(User::getId, Function.identity()));
4042
var databaseGroups = groupRepo.findAll().stream().collect(Collectors.toMap(Group::getId, Function.identity()));
41-
syncAddedUsers(keycloakUsers, databaseUsers);
43+
44+
// sync users:
45+
var addedUsers = syncAddedUsers(keycloakUsers, databaseUsers);
4246
var deletedUserIds = syncDeletedUsers(keycloakUsers, databaseUsers);
4347
syncUpdatedUsers(keycloakUsers, databaseUsers, deletedUserIds);
44-
syncAddedGroups(keycloakGroups, databaseGroups, databaseUsers);
48+
49+
// all users after additions and deletions:
50+
Map<String, Authority> allAuthorities = merge(databaseUsers, addedUsers);
51+
deletedUserIds.forEach(allAuthorities::remove);
52+
53+
// sync groups:
54+
var addedGroups = syncAddedGroups(keycloakGroups, databaseGroups, allAuthorities);
4555
var deletedGroupIds = syncDeletedGroups(keycloakGroups, databaseGroups);
46-
syncUpdatedGroups(keycloakGroups, databaseGroups, deletedGroupIds, databaseUsers);
56+
syncUpdatedGroups(keycloakGroups, databaseGroups, deletedGroupIds, allAuthorities);
4757
}
4858

4959
//visible for testing
50-
void syncAddedUsers(Map<String, KeycloakUserDto> keycloakUsers, Map<String, User> databaseUsers) {
60+
Map<String, User> syncAddedUsers(Map<String, KeycloakUserDto> keycloakUsers, Map<String, User> databaseUsers) {
5161
var addedIds = diff(keycloakUsers.keySet(), databaseUsers.keySet());
5262
var added = addedIds.stream().map(id -> {
5363
var keycloakUser = keycloakUsers.get(id);
@@ -57,9 +67,10 @@ void syncAddedUsers(Map<String, KeycloakUserDto> keycloakUsers, Map<String, User
5767
databaseUser.setEmail(keycloakUser.email());
5868
databaseUser.setPictureUrl(keycloakUser.pictureUrl());
5969
return databaseUser;
60-
});
61-
userRepo.persist(added);
70+
}).collect(Collectors.toMap(User::getId, Function.identity()));
71+
userRepo.persist(added.values());
6272
effectiveGroupMembershipRepo.updateUsers(addedIds);
73+
return added;
6374
}
6475

6576
//visible for testing
@@ -83,30 +94,20 @@ void syncUpdatedUsers(Map<String, KeycloakUserDto> keycloakUsers, Map<String, Us
8394
}
8495

8596
//visible for testing
86-
void syncAddedGroups(Map<String, KeycloakGroupDto> keycloakGroups, Map<String, Group> databaseGroups, Map<String, User> databaseUsers) {
97+
Map<String, Group> syncAddedGroups(Map<String, KeycloakGroupDto> keycloakGroups, Map<String, Group> databaseGroups, Map<String, Authority> allAuthorities) {
8798
var addedIds = diff(keycloakGroups.keySet(), databaseGroups.keySet());
8899
var added = addedIds.stream().map(id -> {
89100
var keycloakGroup = keycloakGroups.get(id);
90101
var databaseGroup = new Group();
91102
databaseGroup.setId(keycloakGroup.id());
92103
databaseGroup.setName(keycloakGroup.name());
93104
databaseGroup.setPictureUrl(keycloakGroup.pictureUrl());
94-
Set<Authority> members = new HashSet<>();
95-
for (var keycloakMember : keycloakGroup.members()) {
96-
var databaseUser = databaseUsers.get(keycloakMember.id());
97-
if (databaseUser == null) {
98-
// User might have been just added, fetch from database
99-
databaseUser = userRepo.findById(keycloakMember.id());
100-
}
101-
if (databaseUser != null) {
102-
members.add(databaseUser);
103-
}
104-
}
105-
databaseGroup.setMembers(members);
105+
databaseGroup.getMembers().addAll(keycloakGroup.members().stream().map(KeycloakUserDto::id).map(allAuthorities::get).collect(Collectors.toSet()));
106106
return databaseGroup;
107-
});
108-
groupRepo.persist(added);
107+
}).collect(Collectors.toMap(Group::getId, Function.identity()));
108+
groupRepo.persist(added.values());
109109
effectiveGroupMembershipRepo.updateGroups(addedIds);
110+
return added;
110111
}
111112

112113
//visible for testing
@@ -118,39 +119,39 @@ Set<String> syncDeletedGroups(Map<String, KeycloakGroupDto> keycloakGroups, Map<
118119
}
119120

120121
//visible for testing
121-
void syncUpdatedGroups(Map<String, KeycloakGroupDto> keycloakGroups, Map<String, Group> databaseGroups, Set<String> deletedGroupIds, Map<String, User> databaseUsers) {
122+
void syncUpdatedGroups(Map<String, KeycloakGroupDto> keycloakGroups, Map<String, Group> databaseGroups, Set<String> deletedGroupIds, Map<String, Authority> allAuthorities) {
122123
var toUpdateIds = diff(databaseGroups.keySet(), deletedGroupIds);
123124
var idsOfGroupsWithChangedMembers = new HashSet<String>();
124125
for (var id : toUpdateIds) {
125126
var databaseGroup = databaseGroups.get(id);
126127
var keycloakGroup = keycloakGroups.get(id);
127-
var wantIds = keycloakGroup.members().stream().map(KeycloakUserDto::id).collect(Collectors.toSet());
128-
var haveIds = databaseGroup.getMembers().stream().map(Authority::getId).collect(Collectors.toSet());
129128
databaseGroup.setName(keycloakGroup.name());
130129
databaseGroup.setPictureUrl(keycloakGroup.pictureUrl());
131-
for (var addId : diff(wantIds, haveIds)) {
132-
var databaseUser = databaseUsers.get(addId);
133-
if (databaseUser == null) {
134-
// User might have been just added, fetch from database
135-
databaseUser = userRepo.findById(addId);
136-
}
137-
if (databaseUser != null) {
138-
databaseGroup.getMembers().add(databaseUser);
139-
}
140-
}
141-
for (var removeId : diff(haveIds, wantIds)) {
142-
databaseGroup.getMembers().removeIf(u -> u.getId().equals(removeId));
143-
}
144-
if (!wantIds.containsAll(haveIds) || !haveIds.containsAll(wantIds)) {
130+
131+
// update members:
132+
var kcMemberIds = keycloakGroup.members().stream().map(KeycloakUserDto::id).collect(Collectors.toSet());
133+
var dbMemberIds = databaseGroup.getMembers().stream().map(Authority::getId).collect(Collectors.toSet());
134+
var addedMemberIds = diff(kcMemberIds, dbMemberIds);
135+
var addedMembers = addedMemberIds.stream().map(allAuthorities::get).collect(Collectors.toSet());
136+
databaseGroup.getMembers().addAll(addedMembers);
137+
var removedMemberIds = diff(dbMemberIds, kcMemberIds);
138+
databaseGroup.getMembers().removeIf(u -> removedMemberIds.contains(u.getId()));
139+
if (!addedMemberIds.isEmpty() || !removedMemberIds.isEmpty()) {
145140
idsOfGroupsWithChangedMembers.add(id);
146141
}
147142
}
148143
effectiveGroupMembershipRepo.updateGroups(idsOfGroupsWithChangedMembers);
149144
}
150145

151-
private <T> Set<T> diff(Set<T> base, Set<T> difference) {
146+
private static <T> Set<T> diff(Set<T> base, Set<T> difference) {
152147
var result = new HashSet<>(base);
153148
result.removeAll(difference);
154149
return result;
155150
}
151+
152+
private static <K, V> Map<K, V> merge(Map<K, ? extends V> first, Map<K, ? extends V> second) {
153+
Map<K, V> result = new HashMap<>(first);
154+
result.putAll(second);
155+
return result;
156+
}
156157
}

backend/src/test/java/org/cryptomator/hub/RollbackTestIT.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class WithFlywayCleanup {
2929
@Test
3030
@Order(1)
3131
@DBRollbackAfter
32-
public void changeDB() throws SQLException {
32+
void changeDB() throws SQLException {
3333
try (var c = dataSource.getConnection(); var s = c.createStatement()) {
3434
s.execute("""
3535
UPDATE "settings"
@@ -41,7 +41,7 @@ public void changeDB() throws SQLException {
4141

4242
@Test
4343
@Order(2)
44-
public void testDB() throws SQLException {
44+
void testDB() throws SQLException {
4545
try (var c = dataSource.getConnection(); var s = c.createStatement()) {
4646
var result = s.executeQuery("""
4747
SELECT *
@@ -60,7 +60,7 @@ class NoCleanup {
6060

6161
@Test
6262
@Order(1)
63-
public void changeDB() throws SQLException {
63+
void changeDB() throws SQLException {
6464
try (var c = dataSource.getConnection(); var s = c.createStatement()) {
6565
s.execute("""
6666
UPDATE "settings"
@@ -72,7 +72,7 @@ public void changeDB() throws SQLException {
7272

7373
@Test
7474
@Order(2)
75-
public void testDB() throws SQLException {
75+
void testDB() throws SQLException {
7676
try (var c = dataSource.getConnection(); var s = c.createStatement()) {
7777
var result = s.executeQuery("""
7878
SELECT *

backend/src/test/java/org/cryptomator/hub/api/AuditLogResourceIT.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,20 @@ public class AuditLogResourceIT {
2424
LicenseHolder licenseHolder;
2525

2626
@BeforeAll
27-
public static void beforeAll() {
27+
static void beforeAll() {
2828
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
2929
}
3030

3131
@BeforeEach
32-
public void beforeEach() {
32+
void beforeEach() {
3333
Mockito.doReturn(true).when(licenseHolder).isSet();
3434
Mockito.doReturn(false).when(licenseHolder).isExpired();
3535
}
3636

3737
@Test
3838
@TestSecurity(user = "Admin", roles = {"admin"})
3939
@DisplayName("As admin, GET /auditlog?startDate=2020-02-20T00:00:00.000Z&endDate=2020-02-20T23:59:59.999Z&paginationId=9999 returns 200 with 20 entries")
40-
public void testGetAuditLogEntries() {
40+
void testGetAuditLogEntries() {
4141
given().param("startDate", "2020-02-20T00:00:00.000Z")
4242
.param("endDate", "2020-02-20T23:59:59.999Z")
4343
.param("paginationId", 9999L)
@@ -49,7 +49,7 @@ public void testGetAuditLogEntries() {
4949
@Test
5050
@TestSecurity(user = "Admin", roles = {"admin"})
5151
@DisplayName("As admin, GET /auditlog?startDate=2020-02-20T00:00:00.000Z&endDate=2020-02-20T23:59:59.999Z&pageSize=10&paginationId=1000&order=asc returns 200 with 3 entries")
52-
public void testGetAuditLogEntriesPageSizeAsc() {
52+
void testGetAuditLogEntriesPageSizeAsc() {
5353
given().param("startDate", "2020-02-20T00:00:00.000Z")
5454
.param("endDate", "2020-02-20T23:59:59.999Z")
5555
.param("pageSize", 3)
@@ -63,7 +63,7 @@ public void testGetAuditLogEntriesPageSizeAsc() {
6363
@Test
6464
@TestSecurity(user = "User", roles = {"user"})
6565
@DisplayName("As user, GET /auditlog?startDate=2020-02-20T00:00:00.000Z&endDate=2020-02-20T23:59:59.999Z&pageSize=10 returns 403")
66-
public void testGetAuditLogEntriesAsUser() {
66+
void testGetAuditLogEntriesAsUser() {
6767
when().get("/auditlog?startDate=2020-02-20T00:00:00.000Z&endDate=2020-02-20T23:59:59.999ZZ&pageSize=10")
6868
.then().statusCode(403);
6969
}

0 commit comments

Comments
 (0)