From 7456d0e27bd804f784fd5c23ca308b17a68a7ffc Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Mon, 5 Aug 2024 19:34:43 +1000 Subject: [PATCH 01/23] Add backend change for listing related keysets --- .../admin/vertx/service/SharingService.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/main/java/com/uid2/admin/vertx/service/SharingService.java b/src/main/java/com/uid2/admin/vertx/service/SharingService.java index cef0692d..55c62436 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SharingService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SharingService.java @@ -3,6 +3,7 @@ import com.uid2.admin.auth.AdminAuthMiddleware; import com.uid2.admin.auth.AdminKeyset; import com.uid2.admin.store.reader.RotatingAdminKeysetStore; +import com.uid2.admin.vertx.RequestUtil; import com.uid2.admin.vertx.WriteLock; import com.uid2.admin.managers.KeysetManager; import com.uid2.admin.vertx.ResponseUtil; @@ -23,6 +24,8 @@ import java.util.*; import java.util.stream.Collectors; +import static com.uid2.admin.vertx.RequestUtil.getTypes; + public class SharingService implements IService { private final AdminAuthMiddleware auth; @@ -70,6 +73,9 @@ public void setupRoutes(Router router) { router.get("/api/sharing/keyset/:keyset_id").handler( auth.handle(this::handleListKeyset, Role.MAINTAINER) ); + router.get("/api/sharing/keysets/related").handler( + auth.handle(this::handleListAllKeysetsRelated, Role.MAINTAINER) + ); } private void handleSetKeyset(RoutingContext rc) { @@ -150,6 +156,65 @@ private void handleSetKeyset(RoutingContext rc) { } } + // Method to check if one set contains any values from another set + private static boolean containsAny(Set set1, Set set2) { + for (T element : set2) { + if (set1.contains(element)) { + return true; + } + } + return false; + } + + private void handleListAllKeysetsRelated(RoutingContext rc) { + try { + // Get value for site id + final Optional siteIdOpt = RequestUtil.getSiteId(rc, "site_id"); + if (!siteIdOpt.isPresent()) return; + final int siteId = siteIdOpt.get(); + + if (siteId != Const.Data.AdvertisingTokenSiteId && !SiteUtil.isValidSiteId(siteId)) { + ResponseUtil.error(rc, 400, "must specify a valid site id"); + return; + } + + // Get value for client type + Set clientTypes = new HashSet<>(); + if (!rc.queryParam("client_types").isEmpty()) { + clientTypes = getTypes(rc.queryParam("client_types").get(0)); + } + + // Check if the key has a ID_READER role + boolean isIdReaderRole = false; + if (rc.queryParam("is_id_reader_role").get(0).equals("true")) { + isIdReaderRole = true; + }; + + // Get the keyset ids that need to be rotated + final JsonArray ja = new JsonArray(); + Map collection = this.keysetProvider.getSnapshot().getAllKeysets(); + for (Map.Entry keyset : collection.entrySet()) { + // The keysets meet any of the below conditions ALL need to be rotated: + // a. Keysets where allowed_types include any of the clientTypes that you noted down earlier + // b. If leaked key was an ID_READER, we want to rotate the keysets where allowed_sites is set to null + // c. Keysets where allowed_sites include the leaked site + // d. Keysets belonging to the leaked site itself (can also get these keysets by putting down the "Site id" and click "List All Keysets By Site Id") + if (containsAny(keyset.getValue().getAllowedTypes(), clientTypes) || + isIdReaderRole && keyset.getValue().getAllowedSites() == null || + keyset.getValue().getAllowedSites() != null && keyset.getValue().getAllowedSites().contains(siteId) || + keyset.getValue().getSiteId() == siteId) { + ja.add(jsonFullKeyset(keyset.getValue())); + } + } + + rc.response() + .putHeader(HttpHeaders.CONTENT_TYPE, "application/json") + .end(ja.encode()); + } catch (Exception e) { + rc.fail(500, e); + } + } + // Returns if a keyset is one of the reserved ones private static boolean isSpecialKeyset(int keysetId) { return keysetId == Const.Data.MasterKeysetId || keysetId == Const.Data.RefreshKeysetId From 92ecbae77f68c9b1f08a329fce5493d457c16bb7 Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Mon, 5 Aug 2024 20:04:25 +1000 Subject: [PATCH 02/23] Improve logic on backend filtering --- .../admin/vertx/service/SharingService.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/uid2/admin/vertx/service/SharingService.java b/src/main/java/com/uid2/admin/vertx/service/SharingService.java index 55c62436..7e232654 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SharingService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SharingService.java @@ -2,6 +2,7 @@ import com.uid2.admin.auth.AdminAuthMiddleware; import com.uid2.admin.auth.AdminKeyset; +import com.uid2.admin.legacy.LegacyClientKey; import com.uid2.admin.store.reader.RotatingAdminKeysetStore; import com.uid2.admin.vertx.RequestUtil; import com.uid2.admin.vertx.WriteLock; @@ -10,6 +11,7 @@ import com.uid2.shared.Const; import com.uid2.shared.auth.Role; import com.uid2.shared.model.ClientType; +import com.uid2.shared.model.Site; import com.uid2.shared.model.SiteUtil; import com.uid2.shared.store.reader.RotatingSiteStore; import io.vertx.core.http.HttpHeaders; @@ -179,16 +181,14 @@ private void handleListAllKeysetsRelated(RoutingContext rc) { } // Get value for client type - Set clientTypes = new HashSet<>(); - if (!rc.queryParam("client_types").isEmpty()) { - clientTypes = getTypes(rc.queryParam("client_types").get(0)); - } + Set clientTypes = this.siteProvider.getSite(siteId).getClientTypes(); + +// // Check if the key has a ID_READER role + boolean isIdReaderRole = true; +// if (rc.queryParam("is_id_reader_role").get(0).equals("true")) { +// isIdReaderRole = true; +// }; - // Check if the key has a ID_READER role - boolean isIdReaderRole = false; - if (rc.queryParam("is_id_reader_role").get(0).equals("true")) { - isIdReaderRole = true; - }; // Get the keyset ids that need to be rotated final JsonArray ja = new JsonArray(); From 413f0651d60b7a3d08d2ea0c1ab1ebcaa2d41f13 Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Mon, 5 Aug 2024 22:51:48 +1000 Subject: [PATCH 03/23] Add frontend to highlight related keysets --- .../admin/vertx/service/SharingService.java | 7 +++-- .../localstack/s3/core/keysets/keysets.json | 18 +++++++++++ webroot/adm/oncall/participant-summary.html | 12 +++++++ webroot/js/participantSummary.js | 31 +++++++++++++++++-- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/uid2/admin/vertx/service/SharingService.java b/src/main/java/com/uid2/admin/vertx/service/SharingService.java index 7e232654..899a1301 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SharingService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SharingService.java @@ -2,7 +2,6 @@ import com.uid2.admin.auth.AdminAuthMiddleware; import com.uid2.admin.auth.AdminKeyset; -import com.uid2.admin.legacy.LegacyClientKey; import com.uid2.admin.store.reader.RotatingAdminKeysetStore; import com.uid2.admin.vertx.RequestUtil; import com.uid2.admin.vertx.WriteLock; @@ -11,7 +10,6 @@ import com.uid2.shared.Const; import com.uid2.shared.auth.Role; import com.uid2.shared.model.ClientType; -import com.uid2.shared.model.Site; import com.uid2.shared.model.SiteUtil; import com.uid2.shared.store.reader.RotatingSiteStore; import io.vertx.core.http.HttpHeaders; @@ -181,7 +179,10 @@ private void handleListAllKeysetsRelated(RoutingContext rc) { } // Get value for client type - Set clientTypes = this.siteProvider.getSite(siteId).getClientTypes(); + Set clientTypes = new HashSet<>(); + if (!rc.queryParam("client_types").isEmpty()) { + clientTypes = getTypes(rc.queryParam("client_types").get(0)); + } // // Check if the key has a ID_READER role boolean isIdReaderRole = true; diff --git a/src/main/resources/localstack/s3/core/keysets/keysets.json b/src/main/resources/localstack/s3/core/keysets/keysets.json index 06bb2fe2..881374fb 100644 --- a/src/main/resources/localstack/s3/core/keysets/keysets.json +++ b/src/main/resources/localstack/s3/core/keysets/keysets.json @@ -89,5 +89,23 @@ "created" : 1617149276, "enabled" : true, "default" : true + }, + { + "keyset_id": 1001, + "site_id": 1000, + "name" : "Legacy Site keyset", + "allowed_sites" : null, + "created" : 1617149276, + "enabled" : true, + "default" : true + }, + { + "keyset_id": 62, + "site_id": 126, + "name": "test", + "allowed_sites": null, + "created": 1694064967, + "enabled": true, + "default": true } ] \ No newline at end of file diff --git a/webroot/adm/oncall/participant-summary.html b/webroot/adm/oncall/participant-summary.html index 16cfe1fe..270855c1 100644 --- a/webroot/adm/oncall/participant-summary.html +++ b/webroot/adm/oncall/participant-summary.html @@ -82,6 +82,18 @@
Participant Opt-out Webhook

                     
                 
+                
+
+
Participant Related Keysets
+
A keyset is related to the participant if it matches below criteria:
+ a. Keysets where allowed_types include any of the clientTypes that you noted down earlier
+ b. If leaked key was an ID_READER, we want to rotate the keysets where allowed_sites is set to null
+ c. Keysets where allowed_sites include the leaked site
+ d. Keysets belonging to the leaked site itself
+

+                        

+                    
+
diff --git a/webroot/js/participantSummary.js b/webroot/js/participantSummary.js index ca40dba3..5d18900c 100644 --- a/webroot/js/participantSummary.js +++ b/webroot/js/participantSummary.js @@ -7,7 +7,7 @@ function participantSummaryErrorHandler(err, divContainer) { }; function loadAllSitesCallback(result) { - siteList = JSON.parse(result).map((item) => { return { name: item.name, id: item.id } }); + siteList = JSON.parse(result).map((item) => { return { name: item.name, id: item.id, clientTypes: item.clientTypes } }); }; function loadSiteCallback(result) { @@ -88,6 +88,31 @@ function loadOptoutWebhooksCallback(result, siteName) { $('#webhooksStandardOutput').html(formatted); }; +function loadRelatedKeysetsCallback(result, siteId, clientTypes) { + const resultJson = JSON.parse(result); + resultJson.forEach(obj => { + // Keysets where allowed_types include any of the clientTypes that belows to the site + obj.allowed_types = obj.allowed_types.map(item => { + return clientTypes.includes(item) ? `${item}` : item; + }); + // Keysets where allowed_sites include the leaked site. As it's an integer object, change it to a placeholder and replace later. + if (obj.allowed_sites) { + obj.allowed_sites = obj.allowed_sites.map(item => { + return item === siteId ? "" : item; + }); + } + }); + const formatted = prettifyJson(JSON.stringify(resultJson)); + let highlightedText = formatted; + // Highlight ketsets where allowed_sites is set to null + highlightedText = highlightedText.replaceAll(`"allowed_sites": null`, '' + `"allowed_sites": null` + ''); + // Highlight keysets where allowed_sites include the leaked site + highlightedText = highlightedText.replaceAll(`""`, `${siteId}`); + // Highlight keysets belonging to the leaked site itself + highlightedText = highlightedText.replaceAll(`"site_id": ${siteId}`, '' + `"site_id": ${siteId}` + ''); + $('#relatedKeysetsStandardOutput').html(highlightedText); +}; + $(document).ready(() => { const sitesUrl = '/api/site/list'; doApiCallWithCallback('GET', sitesUrl, loadAllSitesCallback, null); @@ -123,7 +148,9 @@ $(document).ready(() => { url = '/api/partner_config/get'; doApiCallWithCallback('GET', url, (r) => { loadOptoutWebhooksCallback(r, site.name) }, (err) => { participantSummaryErrorHandler(err, '#webhooksErrorOutput') }); - + + url = `/api/sharing/keysets/related?site_id=${site.id}&client_types=${site.clientTypes}`; + doApiCallWithCallback('GET', url, (r) => { loadRelatedKeysetsCallback(r, site.id, site.clientTypes) }, (err) => { participantSummaryErrorHandler(err, '#relatedKeysetsErrorOutput') }); $('.section').show(); }); }); \ No newline at end of file From 837ddbb0b82abe2aad9f2b43c917c121849bea89 Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Mon, 5 Aug 2024 23:16:10 +1000 Subject: [PATCH 04/23] Add functionality to rotate keysets --- webroot/adm/oncall/participant-summary.html | 8 ++++++++ webroot/js/participantSummary.js | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/webroot/adm/oncall/participant-summary.html b/webroot/adm/oncall/participant-summary.html index 270855c1..3491eaff 100644 --- a/webroot/adm/oncall/participant-summary.html +++ b/webroot/adm/oncall/participant-summary.html @@ -92,6 +92,14 @@
Participant Related Keysets
d. Keysets belonging to the leaked site itself

                         

+
+                        
+ Rotate Keysets +
+

+                                

+                            
+
diff --git a/webroot/js/participantSummary.js b/webroot/js/participantSummary.js index 5d18900c..451a3179 100644 --- a/webroot/js/participantSummary.js +++ b/webroot/js/participantSummary.js @@ -153,4 +153,16 @@ $(document).ready(() => { doApiCallWithCallback('GET', url, (r) => { loadRelatedKeysetsCallback(r, site.id, site.clientTypes) }, (err) => { participantSummaryErrorHandler(err, '#relatedKeysetsErrorOutput') }); $('.section').show(); }); + + $('#doRotateKeysets').on('click', () => { + var keysets = $('#relatedKeysetsStandardOutput').text(); + console.log(keysets); + const ja = JSON.parse(keysets); + ja.forEach((keyset) => { + var url = `/api/key/rotate_keyset_key?min_age_seconds=3600&keyset_id=${keyset.keyset_id}&force=true`; +// if ($('#force').is(':checked')) url = url + '&force=true'; + + doApiCall('POST', url, '#standardOutput', '#errorOutput'); + }); + }); }); \ No newline at end of file From 454aa907cb03743b4ce01df9e40b0c1ee718cfec Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Tue, 6 Aug 2024 15:17:31 +1000 Subject: [PATCH 05/23] Get ClientTypes from backend --- .../java/com/uid2/admin/vertx/service/SharingService.java | 5 +---- webroot/js/participantSummary.js | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/uid2/admin/vertx/service/SharingService.java b/src/main/java/com/uid2/admin/vertx/service/SharingService.java index 899a1301..f114395d 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SharingService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SharingService.java @@ -179,10 +179,7 @@ private void handleListAllKeysetsRelated(RoutingContext rc) { } // Get value for client type - Set clientTypes = new HashSet<>(); - if (!rc.queryParam("client_types").isEmpty()) { - clientTypes = getTypes(rc.queryParam("client_types").get(0)); - } + Set clientTypes = this.siteProvider.getSite(siteId).getClientTypes(); // // Check if the key has a ID_READER role boolean isIdReaderRole = true; diff --git a/webroot/js/participantSummary.js b/webroot/js/participantSummary.js index 451a3179..a22f2a6b 100644 --- a/webroot/js/participantSummary.js +++ b/webroot/js/participantSummary.js @@ -149,7 +149,7 @@ $(document).ready(() => { url = '/api/partner_config/get'; doApiCallWithCallback('GET', url, (r) => { loadOptoutWebhooksCallback(r, site.name) }, (err) => { participantSummaryErrorHandler(err, '#webhooksErrorOutput') }); - url = `/api/sharing/keysets/related?site_id=${site.id}&client_types=${site.clientTypes}`; + url = `/api/sharing/keysets/related?site_id=${site.id}`; doApiCallWithCallback('GET', url, (r) => { loadRelatedKeysetsCallback(r, site.id, site.clientTypes) }, (err) => { participantSummaryErrorHandler(err, '#relatedKeysetsErrorOutput') }); $('.section').show(); }); From dce0e231d8b6f9510c7098dd8c2539a71b94fe5d Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Tue, 6 Aug 2024 15:17:50 +1000 Subject: [PATCH 06/23] Remove condition for force check --- webroot/js/participantSummary.js | 1 - 1 file changed, 1 deletion(-) diff --git a/webroot/js/participantSummary.js b/webroot/js/participantSummary.js index a22f2a6b..de1d0b38 100644 --- a/webroot/js/participantSummary.js +++ b/webroot/js/participantSummary.js @@ -160,7 +160,6 @@ $(document).ready(() => { const ja = JSON.parse(keysets); ja.forEach((keyset) => { var url = `/api/key/rotate_keyset_key?min_age_seconds=3600&keyset_id=${keyset.keyset_id}&force=true`; -// if ($('#force').is(':checked')) url = url + '&force=true'; doApiCall('POST', url, '#standardOutput', '#errorOutput'); }); From dce110916e72c8b25f80ffa2ec7436e018267a13 Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Tue, 6 Aug 2024 15:21:09 +1000 Subject: [PATCH 07/23] Check if a site has any client key that has ID_READER role --- src/main/java/com/uid2/admin/Main.java | 2 +- .../admin/vertx/service/SharingService.java | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/uid2/admin/Main.java b/src/main/java/com/uid2/admin/Main.java index 5568852e..1f5472f0 100644 --- a/src/main/java/com/uid2/admin/Main.java +++ b/src/main/java/com/uid2/admin/Main.java @@ -251,7 +251,7 @@ public void run() { new EnclaveIdService(auth, writeLock, enclaveStoreWriter, enclaveIdProvider, clock), encryptionKeyService, new KeyAclService(auth, writeLock, keyAclStoreWriter, keyAclProvider, siteProvider, encryptionKeyService), - new SharingService(auth, writeLock, adminKeysetProvider, keysetManager, siteProvider, enableKeysets), + new SharingService(auth, writeLock, adminKeysetProvider, keysetManager, siteProvider, enableKeysets, clientKeyProvider), clientSideKeypairService, new ServiceService(auth, writeLock, serviceStoreWriter, serviceProvider, siteProvider, serviceLinkProvider), new ServiceLinkService(auth, writeLock, serviceLinkStoreWriter, serviceLinkProvider, serviceProvider, siteProvider), diff --git a/src/main/java/com/uid2/admin/vertx/service/SharingService.java b/src/main/java/com/uid2/admin/vertx/service/SharingService.java index f114395d..667d6c75 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SharingService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SharingService.java @@ -2,6 +2,8 @@ import com.uid2.admin.auth.AdminAuthMiddleware; import com.uid2.admin.auth.AdminKeyset; +import com.uid2.admin.legacy.LegacyClientKey; +import com.uid2.admin.legacy.RotatingLegacyClientKeyProvider; import com.uid2.admin.store.reader.RotatingAdminKeysetStore; import com.uid2.admin.vertx.RequestUtil; import com.uid2.admin.vertx.WriteLock; @@ -33,6 +35,7 @@ public class SharingService implements IService { private final RotatingAdminKeysetStore keysetProvider; private final RotatingSiteStore siteProvider; private final KeysetManager keysetManager; + private final RotatingLegacyClientKeyProvider clientKeyProvider; private static final Logger LOGGER = LoggerFactory.getLogger(SharingService.class); private final boolean enableKeysets; @@ -42,13 +45,15 @@ public SharingService(AdminAuthMiddleware auth, RotatingAdminKeysetStore keysetProvider, KeysetManager keysetManager, RotatingSiteStore siteProvider, - boolean enableKeyset) { + boolean enableKeyset, + RotatingLegacyClientKeyProvider clientKeyProvider) { this.auth = auth; this.writeLock = writeLock; this.keysetProvider = keysetProvider; this.keysetManager = keysetManager; this.siteProvider = siteProvider; this.enableKeysets = enableKeyset; + this.clientKeyProvider = clientKeyProvider; } @Override @@ -181,12 +186,13 @@ private void handleListAllKeysetsRelated(RoutingContext rc) { // Get value for client type Set clientTypes = this.siteProvider.getSite(siteId).getClientTypes(); -// // Check if the key has a ID_READER role - boolean isIdReaderRole = true; -// if (rc.queryParam("is_id_reader_role").get(0).equals("true")) { -// isIdReaderRole = true; -// }; - + // Check if this site has any cleint key that has an ID_READER role + boolean isIdReaderRole = false; + for (LegacyClientKey c : this.clientKeyProvider.getAll()) { + if (c.getRoles().contains("ID_READER")) { + isIdReaderRole = true; + } + } // Get the keyset ids that need to be rotated final JsonArray ja = new JsonArray(); From 950d2a8176840434ca86b6dabbf4dd3614421b49 Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Tue, 6 Aug 2024 19:13:41 +1000 Subject: [PATCH 08/23] Add helper text to explain min age and force option --- webroot/adm/oncall/participant-summary.html | 1 + webroot/js/participantSummary.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/webroot/adm/oncall/participant-summary.html b/webroot/adm/oncall/participant-summary.html index 3491eaff..71a8f977 100644 --- a/webroot/adm/oncall/participant-summary.html +++ b/webroot/adm/oncall/participant-summary.html @@ -95,6 +95,7 @@
Participant Related Keysets
Rotate Keysets +
Note: Once rotated, the participant will need to get a new refresh key to be able to decrypt data.

                                 

diff --git a/webroot/js/participantSummary.js b/webroot/js/participantSummary.js
index de1d0b38..045e4a64 100644
--- a/webroot/js/participantSummary.js
+++ b/webroot/js/participantSummary.js
@@ -159,7 +159,7 @@ $(document).ready(() => {
         console.log(keysets);
         const ja = JSON.parse(keysets);
         ja.forEach((keyset) => {
-            var url = `/api/key/rotate_keyset_key?min_age_seconds=3600&keyset_id=${keyset.keyset_id}&force=true`;
+            var url = `/api/key/rotate_keyset_key?min_age_seconds=0&keyset_id=${keyset.keyset_id}&force=true`;
 
             doApiCall('POST', url, '#standardOutput', '#errorOutput');
         });

From 5ddf6b0278e16cfcefc4e30d191ce5d1b0d993be Mon Sep 17 00:00:00 2001
From: Katherine Chen 
Date: Tue, 6 Aug 2024 19:35:19 +1000
Subject: [PATCH 09/23] Check ID_READER role with correct type

---
 src/main/java/com/uid2/admin/vertx/service/SharingService.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/com/uid2/admin/vertx/service/SharingService.java b/src/main/java/com/uid2/admin/vertx/service/SharingService.java
index 667d6c75..fbd91328 100644
--- a/src/main/java/com/uid2/admin/vertx/service/SharingService.java
+++ b/src/main/java/com/uid2/admin/vertx/service/SharingService.java
@@ -189,7 +189,7 @@ private void handleListAllKeysetsRelated(RoutingContext rc) {
             // Check if this site has any cleint key that has an ID_READER role
             boolean isIdReaderRole = false;
             for (LegacyClientKey c : this.clientKeyProvider.getAll()) {
-                if (c.getRoles().contains("ID_READER")) {
+                if (c.getRoles().contains(Role.ID_READER)) {
                     isIdReaderRole = true;
                 }
             }

From 056f414a3200aaf168ed461392229bbde406cd20 Mon Sep 17 00:00:00 2001
From: Katherine Chen 
Date: Tue, 6 Aug 2024 20:37:05 +1000
Subject: [PATCH 10/23] Add tests for related keyset api

---
 .../admin/vertx/ClientKeyServiceTest.java     |   2 +-
 .../uid2/admin/vertx/SharingServiceTest.java  | 171 +++++++++++++++++-
 2 files changed, 165 insertions(+), 8 deletions(-)

diff --git a/src/test/java/com/uid2/admin/vertx/ClientKeyServiceTest.java b/src/test/java/com/uid2/admin/vertx/ClientKeyServiceTest.java
index a6ada25e..6f321641 100644
--- a/src/test/java/com/uid2/admin/vertx/ClientKeyServiceTest.java
+++ b/src/test/java/com/uid2/admin/vertx/ClientKeyServiceTest.java
@@ -460,7 +460,7 @@ private static void assertAddedClientKeyEquals(ClientKey expected, ClientKey act
                 .isEqualTo(expected);
     }
 
-    private static class LegacyClientBuilder {
+    public static class LegacyClientBuilder {
         private String name = "test_client";
         private String contact = "test_contact";
         private int siteId = 999;
diff --git a/src/test/java/com/uid2/admin/vertx/SharingServiceTest.java b/src/test/java/com/uid2/admin/vertx/SharingServiceTest.java
index a41ff1c5..52ebc766 100644
--- a/src/test/java/com/uid2/admin/vertx/SharingServiceTest.java
+++ b/src/test/java/com/uid2/admin/vertx/SharingServiceTest.java
@@ -21,23 +21,19 @@
 import org.junit.jupiter.params.provider.ValueSource;
 
 import java.time.Instant;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.*;
 
 public class SharingServiceTest extends ServiceTestBase {
     @Override
     protected IService createService() {
         KeysetManager keysetManager = new KeysetManager(adminKeysetProvider, adminKeysetWriter, keysetKeyManager, true);
-        return new SharingService(auth, writeLock, adminKeysetProvider, keysetManager, siteProvider, true);
+        return new SharingService(auth, writeLock, adminKeysetProvider, keysetManager, siteProvider, true, clientKeyProvider);
     }
 
     private void compareKeysetListToResult(AdminKeyset keyset, JsonArray actualList) {
@@ -55,6 +51,14 @@ private void compareKeysetTypeListToResult(AdminKeyset keyset, JsonArray actualL
                 .collect(Collectors.toSet());
         assertEquals(keyset.getAllowedTypes(), actualSet);
     }
+
+    private void compareKeysetsIdToResult(Set expectedKeysets, JsonArray actualList) {
+        assertNotNull(actualList);
+        Set actualSet = actualList.stream()
+                .map(s -> (AdminKeyset) s)
+                .collect(Collectors.toSet());
+        assertEquals(true, actualSet.contains(expectedKeysets));
+    }
     
     private void mockSiteExistence(Integer... sites){
         for(Integer site : sites) {
@@ -1259,4 +1263,157 @@ void KeysetSetNewWithType(Vertx vertx, VertxTestContext testContext) {
             testContext.completeNow();
         });
     }
+
+    @Test
+    void RelatedKeysetSetsWithClientTypes(Vertx vertx, VertxTestContext testContext) {
+        fakeAuth(Role.MAINTAINER);
+        when(clock.getEpochSecond()).thenReturn(1722938135L);
+
+        AdminKeyset adminKeyset1 = new AdminKeyset(3, 5, "test", Set.of(4,6,7), clock.getEpochSecond(),true, true, new HashSet<>(Arrays.asList(ClientType.DSP, ClientType.PUBLISHER)));
+        AdminKeyset adminKeyset2 = new AdminKeyset(4, 7, "test", Set.of(12), clock.getEpochSecond(),true, true, new HashSet<>(Arrays.asList(ClientType.DSP)));
+        AdminKeyset adminKeyset3 = new AdminKeyset(5, 4, "test", Set.of(5), clock.getEpochSecond(),true, true, new HashSet<>());
+
+        Map keysets = new HashMap() {{
+            put(3, adminKeyset1);
+            put(4, adminKeyset2);
+            put(5, adminKeyset3);
+        }};
+
+        setAdminKeysets(keysets);
+        mockSiteExistence(5, 7, 4, 8, 22, 25, 6);
+        doReturn(new Site(8, "test-name", true, new HashSet<>(Arrays.asList(ClientType.DSP)), null)).when(siteProvider).getSite(8);
+
+
+        get(vertx, testContext, "/api/sharing/keysets/related?site_id=8", response -> {
+            assertEquals(200, response.statusCode());
+
+            Set expectedKeysetIds = new HashSet<>(Arrays.asList(adminKeyset1.getKeysetId(), adminKeyset2.getKeysetId()));
+
+            Set actualKeysetIds = new HashSet<>();
+            JsonArray responseArray = response.bodyAsJsonArray();
+            for (int i = 0; i < responseArray.size(); i++) {
+                JsonObject item = responseArray.getJsonObject(i);
+                int keysetId = item.getInteger("keyset_id");
+                actualKeysetIds.add(keysetId);
+            }
+            assertEquals(true, actualKeysetIds.containsAll(expectedKeysetIds));
+            testContext.completeNow();
+        });
+    }
+
+    @Test
+    void RelatedKeysetSetsWithAllowedSites(Vertx vertx, VertxTestContext testContext) {
+        fakeAuth(Role.MAINTAINER);
+        when(clock.getEpochSecond()).thenReturn(1722938135L);
+
+        AdminKeyset adminKeyset1 = new AdminKeyset(3, 1, "test", Set.of(4,8), clock.getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset2 = new AdminKeyset(4, 2, "test", Set.of(5,8), clock.getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset3 = new AdminKeyset(5, 3, "test", Set.of(6), clock.getEpochSecond(),true, true, new HashSet<>());
+
+        Map keysets = new HashMap() {{
+            put(3, adminKeyset1);
+            put(4, adminKeyset2);
+            put(5, adminKeyset3);
+        }};
+
+        setAdminKeysets(keysets);
+        mockSiteExistence(1,2,3,4,5,6,8);
+        doReturn(new Site(8, "test-name", true,null)).when(siteProvider).getSite(8);
+
+
+        get(vertx, testContext, "/api/sharing/keysets/related?site_id=8", response -> {
+            assertEquals(200, response.statusCode());
+
+            Set expectedKeysetIds = new HashSet<>(Arrays.asList(adminKeyset1.getKeysetId(), adminKeyset2.getKeysetId()));
+
+            Set actualKeysetIds = new HashSet<>();
+            JsonArray responseArray = response.bodyAsJsonArray();
+            for (int i = 0; i < responseArray.size(); i++) {
+                JsonObject item = responseArray.getJsonObject(i);
+                int keysetId = item.getInteger("keyset_id");
+                actualKeysetIds.add(keysetId);
+            }
+            assertEquals(true, actualKeysetIds.containsAll(expectedKeysetIds));
+            testContext.completeNow();
+        });
+    }
+
+    @Test
+    void RelatedKeysetSetsWithSameSiteId(Vertx vertx, VertxTestContext testContext) {
+        fakeAuth(Role.MAINTAINER);
+        when(clock.getEpochSecond()).thenReturn(1722938135L);
+
+        AdminKeyset adminKeyset1 = new AdminKeyset(3, 1, "test", Set.of(4), clock.getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset2 = new AdminKeyset(4, 2, "test", Set.of(5), clock.getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset3 = new AdminKeyset(5, 8, "test", Set.of(6), clock.getEpochSecond(),true, true, new HashSet<>());
+
+        Map keysets = new HashMap() {{
+            put(3, adminKeyset1);
+            put(4, adminKeyset2);
+            put(5, adminKeyset3);
+        }};
+
+        setAdminKeysets(keysets);
+        mockSiteExistence(1,2,4,5,6,8);
+        doReturn(new Site(8, "test-name", true,null)).when(siteProvider).getSite(8);
+
+
+        get(vertx, testContext, "/api/sharing/keysets/related?site_id=8", response -> {
+            assertEquals(200, response.statusCode());
+
+            Set expectedKeysetIds = new HashSet<>(Arrays.asList(adminKeyset3.getKeysetId()));
+
+            Set actualKeysetIds = new HashSet<>();
+            JsonArray responseArray = response.bodyAsJsonArray();
+            for (int i = 0; i < responseArray.size(); i++) {
+                JsonObject item = responseArray.getJsonObject(i);
+                int keysetId = item.getInteger("keyset_id");
+                actualKeysetIds.add(keysetId);
+            }
+            assertEquals(true, actualKeysetIds.containsAll(expectedKeysetIds));
+            testContext.completeNow();
+        });
+    }
+
+    @Test
+    void RelatedKeysetSetsWithAllowSiteNull(Vertx vertx, VertxTestContext testContext) {
+        fakeAuth(Role.MAINTAINER);
+        when(clock.getEpochSecond()).thenReturn(1722938135L);
+
+        AdminKeyset adminKeyset1 = new AdminKeyset(3, 1, "test", Set.of(4), clock.getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset2 = new AdminKeyset(4, 2, "test", Set.of(5), clock.getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset3 = new AdminKeyset(5, 3, "test", null, clock.getEpochSecond(),true, true, new HashSet<>());
+
+        Map keysets = new HashMap() {{
+            put(3, adminKeyset1);
+            put(4, adminKeyset2);
+            put(5, adminKeyset3);
+        }};
+
+        setAdminKeysets(keysets);
+        mockSiteExistence(1,2,3,4,5,8);
+        doReturn(new Site(8, "test-name", true,null)).when(siteProvider).getSite(8);
+        setClientKeys(
+                new ClientKeyServiceTest.LegacyClientBuilder()
+                        .withRoles(new HashSet<>(Arrays.asList(Role.ID_READER)))
+                        .withSiteId(8)
+                        .build());
+
+
+        get(vertx, testContext, "/api/sharing/keysets/related?site_id=8", response -> {
+            assertEquals(200, response.statusCode());
+
+            Set expectedKeysetIds = new HashSet<>(Arrays.asList(adminKeyset3.getKeysetId()));
+
+            Set actualKeysetIds = new HashSet<>();
+            JsonArray responseArray = response.bodyAsJsonArray();
+            for (int i = 0; i < responseArray.size(); i++) {
+                JsonObject item = responseArray.getJsonObject(i);
+                int keysetId = item.getInteger("keyset_id");
+                actualKeysetIds.add(keysetId);
+            }
+            assertEquals(true, actualKeysetIds.containsAll(expectedKeysetIds));
+            testContext.completeNow();
+        });
+    }
 }

From 9c215fceacb11df19cdcb154c91ef4115ddfc7e1 Mon Sep 17 00:00:00 2001
From: Katherine Chen 
Date: Tue, 6 Aug 2024 20:42:16 +1000
Subject: [PATCH 11/23] Update comments

---
 .../com/uid2/admin/vertx/service/SharingService.java   | 10 +++++-----
 webroot/adm/oncall/participant-summary.html            |  8 ++++----
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/main/java/com/uid2/admin/vertx/service/SharingService.java b/src/main/java/com/uid2/admin/vertx/service/SharingService.java
index fbd91328..31044bce 100644
--- a/src/main/java/com/uid2/admin/vertx/service/SharingService.java
+++ b/src/main/java/com/uid2/admin/vertx/service/SharingService.java
@@ -183,10 +183,10 @@ private void handleListAllKeysetsRelated(RoutingContext rc) {
                 return;
             }
 
-            // Get value for client type
+            // Get value for client type from the backend
             Set clientTypes = this.siteProvider.getSite(siteId).getClientTypes();
 
-            // Check if this site has any cleint key that has an ID_READER role
+            // Check if this site has any client key that has an ID_READER role
             boolean isIdReaderRole = false;
             for (LegacyClientKey c : this.clientKeyProvider.getAll()) {
                 if (c.getRoles().contains(Role.ID_READER)) {
@@ -199,10 +199,10 @@ private void handleListAllKeysetsRelated(RoutingContext rc) {
             Map collection = this.keysetProvider.getSnapshot().getAllKeysets();
             for (Map.Entry keyset : collection.entrySet()) {
                 // The keysets meet any of the below conditions ALL need to be rotated:
-                // a. Keysets where allowed_types include any of the clientTypes that you noted down earlier
-                // b. If leaked key was an ID_READER, we want to rotate the keysets where allowed_sites is set to null
+                // a. Keysets where allowed_types include any of the clientTypes of the site
+                // b. If this participant has a client key with ID_READER role, we want to rotate all the keysets where allowed_sites is set to null
                 // c. Keysets where allowed_sites include the leaked site
-                // d. Keysets belonging to the leaked site itself (can also get these keysets by putting down the "Site id" and click "List All Keysets By Site Id")
+                // d. Keysets belonging to the leaked site itself
                 if (containsAny(keyset.getValue().getAllowedTypes(), clientTypes) ||
                         isIdReaderRole && keyset.getValue().getAllowedSites() == null ||
                         keyset.getValue().getAllowedSites() != null && keyset.getValue().getAllowedSites().contains(siteId) ||
diff --git a/webroot/adm/oncall/participant-summary.html b/webroot/adm/oncall/participant-summary.html
index 71a8f977..df322b7d 100644
--- a/webroot/adm/oncall/participant-summary.html
+++ b/webroot/adm/oncall/participant-summary.html
@@ -86,10 +86,10 @@ 
Participant Opt-out Webhook
Participant Related Keysets
A keyset is related to the participant if it matches below criteria:
- a. Keysets where allowed_types include any of the clientTypes that you noted down earlier
- b. If leaked key was an ID_READER, we want to rotate the keysets where allowed_sites is set to null
- c. Keysets where allowed_sites include the leaked site
- d. Keysets belonging to the leaked site itself
+ a. Keysets where allowed_types include any of the clientTypes of the site
+ b. If this participant has a client key with ID_READER role, we want to rotate all the keysets where allowed_sites is set to null
+ c. Keysets where allowed_sites include the leaked site
+ d. Keysets belonging to the leaked site itself

                         

 

From d25ca9ac35f32d65cc424b9cf822e80745677a1b Mon Sep 17 00:00:00 2001
From: Katherine Chen 
Date: Tue, 6 Aug 2024 21:16:07 +1000
Subject: [PATCH 12/23] Update highlights for client types

---
 webroot/js/participantSummary.js | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/webroot/js/participantSummary.js b/webroot/js/participantSummary.js
index 045e4a64..c4008063 100644
--- a/webroot/js/participantSummary.js
+++ b/webroot/js/participantSummary.js
@@ -92,10 +92,14 @@ function loadRelatedKeysetsCallback(result, siteId, clientTypes) {
     const resultJson = JSON.parse(result);
     resultJson.forEach(obj => {
         // Keysets where allowed_types include any of the clientTypes that belows to the site
+        console.log(obj.allowed_types);
         obj.allowed_types = obj.allowed_types.map(item => {
-            return clientTypes.includes(item) ? `${item}` : item;
+            return clientTypes.includes(item) ? `` : item;
         });
+        console.log(obj.allowed_types);
+
         // Keysets where allowed_sites include the leaked site. As it's an integer object, change it to a placeholder and replace later.
+        console.log("kat");
         if (obj.allowed_sites) {
             obj.allowed_sites = obj.allowed_sites.map(item => {
                 return item === siteId ? "" : item;
@@ -106,6 +110,11 @@ function loadRelatedKeysetsCallback(result, siteId, clientTypes) {
     let highlightedText = formatted;
     // Highlight ketsets where allowed_sites is set to null
     highlightedText = highlightedText.replaceAll(`"allowed_sites": null`, '' + `"allowed_sites": null` + '');
+    // Highlight keysets where allowed_types include the site client_types
+    highlightedText = highlightedText.replaceAll(`""`, `"DSP"`);
+    highlightedText = highlightedText.replaceAll(`""`, `"ADVERTISER"`);
+    highlightedText = highlightedText.replaceAll(`""`, `"DATA_PROVIDER"`);
+    highlightedText = highlightedText.replaceAll(`""`, `"PUBLISHER"`);
     // Highlight keysets where allowed_sites include the leaked site
     highlightedText = highlightedText.replaceAll(`""`, `${siteId}`);
     // Highlight keysets belonging to the leaked site itself

From 3234fe87f74c492bf468e070a6abe149cd6b0c56 Mon Sep 17 00:00:00 2001
From: Katherine Chen 
Date: Tue, 6 Aug 2024 21:16:49 +1000
Subject: [PATCH 13/23] Revert changes for keyset.json

---
 .../localstack/s3/core/keysets/keysets.json    | 18 ------------------
 1 file changed, 18 deletions(-)

diff --git a/src/main/resources/localstack/s3/core/keysets/keysets.json b/src/main/resources/localstack/s3/core/keysets/keysets.json
index 881374fb..06bb2fe2 100644
--- a/src/main/resources/localstack/s3/core/keysets/keysets.json
+++ b/src/main/resources/localstack/s3/core/keysets/keysets.json
@@ -89,23 +89,5 @@
     "created" : 1617149276,
     "enabled" : true,
     "default" : true
-  },
-  {
-    "keyset_id": 1001,
-    "site_id": 1000,
-    "name" : "Legacy Site keyset",
-    "allowed_sites" : null,
-    "created" : 1617149276,
-    "enabled" : true,
-    "default" : true
-  },
-  {
-    "keyset_id": 62,
-    "site_id": 126,
-    "name": "test",
-    "allowed_sites": null,
-    "created": 1694064967,
-    "enabled": true,
-    "default": true
   }
 ]
\ No newline at end of file

From 2e05764af0dea3f7028d1af2861279ff057fd1d3 Mon Sep 17 00:00:00 2001
From: Katherine Chen 
Date: Tue, 6 Aug 2024 21:17:48 +1000
Subject: [PATCH 14/23] Revert unused functions

---
 .../java/com/uid2/admin/vertx/SharingServiceTest.java     | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/src/test/java/com/uid2/admin/vertx/SharingServiceTest.java b/src/test/java/com/uid2/admin/vertx/SharingServiceTest.java
index 52ebc766..f2ae026b 100644
--- a/src/test/java/com/uid2/admin/vertx/SharingServiceTest.java
+++ b/src/test/java/com/uid2/admin/vertx/SharingServiceTest.java
@@ -51,14 +51,6 @@ private void compareKeysetTypeListToResult(AdminKeyset keyset, JsonArray actualL
                 .collect(Collectors.toSet());
         assertEquals(keyset.getAllowedTypes(), actualSet);
     }
-
-    private void compareKeysetsIdToResult(Set expectedKeysets, JsonArray actualList) {
-        assertNotNull(actualList);
-        Set actualSet = actualList.stream()
-                .map(s -> (AdminKeyset) s)
-                .collect(Collectors.toSet());
-        assertEquals(true, actualSet.contains(expectedKeysets));
-    }
     
     private void mockSiteExistence(Integer... sites){
         for(Integer site : sites) {

From 17f4b8c299175bca599b8ab0e22f2c804963789f Mon Sep 17 00:00:00 2001
From: Katherine Chen 
Date: Tue, 6 Aug 2024 21:19:48 +1000
Subject: [PATCH 15/23] Revert unused clock

---
 .../uid2/admin/vertx/SharingServiceTest.java  | 28 ++++++++-----------
 1 file changed, 12 insertions(+), 16 deletions(-)

diff --git a/src/test/java/com/uid2/admin/vertx/SharingServiceTest.java b/src/test/java/com/uid2/admin/vertx/SharingServiceTest.java
index f2ae026b..3c3002de 100644
--- a/src/test/java/com/uid2/admin/vertx/SharingServiceTest.java
+++ b/src/test/java/com/uid2/admin/vertx/SharingServiceTest.java
@@ -1259,11 +1259,10 @@ void KeysetSetNewWithType(Vertx vertx, VertxTestContext testContext) {
     @Test
     void RelatedKeysetSetsWithClientTypes(Vertx vertx, VertxTestContext testContext) {
         fakeAuth(Role.MAINTAINER);
-        when(clock.getEpochSecond()).thenReturn(1722938135L);
 
-        AdminKeyset adminKeyset1 = new AdminKeyset(3, 5, "test", Set.of(4,6,7), clock.getEpochSecond(),true, true, new HashSet<>(Arrays.asList(ClientType.DSP, ClientType.PUBLISHER)));
-        AdminKeyset adminKeyset2 = new AdminKeyset(4, 7, "test", Set.of(12), clock.getEpochSecond(),true, true, new HashSet<>(Arrays.asList(ClientType.DSP)));
-        AdminKeyset adminKeyset3 = new AdminKeyset(5, 4, "test", Set.of(5), clock.getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset1 = new AdminKeyset(3, 5, "test", Set.of(4,6,7), Instant.now().getEpochSecond(),true, true, new HashSet<>(Arrays.asList(ClientType.DSP, ClientType.PUBLISHER)));
+        AdminKeyset adminKeyset2 = new AdminKeyset(4, 7, "test", Set.of(12), Instant.now().getEpochSecond(),true, true, new HashSet<>(Arrays.asList(ClientType.DSP)));
+        AdminKeyset adminKeyset3 = new AdminKeyset(5, 4, "test", Set.of(5), Instant.now().getEpochSecond(),true, true, new HashSet<>());
 
         Map keysets = new HashMap() {{
             put(3, adminKeyset1);
@@ -1296,11 +1295,10 @@ void RelatedKeysetSetsWithClientTypes(Vertx vertx, VertxTestContext testContext)
     @Test
     void RelatedKeysetSetsWithAllowedSites(Vertx vertx, VertxTestContext testContext) {
         fakeAuth(Role.MAINTAINER);
-        when(clock.getEpochSecond()).thenReturn(1722938135L);
 
-        AdminKeyset adminKeyset1 = new AdminKeyset(3, 1, "test", Set.of(4,8), clock.getEpochSecond(),true, true, new HashSet<>());
-        AdminKeyset adminKeyset2 = new AdminKeyset(4, 2, "test", Set.of(5,8), clock.getEpochSecond(),true, true, new HashSet<>());
-        AdminKeyset adminKeyset3 = new AdminKeyset(5, 3, "test", Set.of(6), clock.getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset1 = new AdminKeyset(3, 1, "test", Set.of(4,8), Instant.now().getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset2 = new AdminKeyset(4, 2, "test", Set.of(5,8), Instant.now().getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset3 = new AdminKeyset(5, 3, "test", Set.of(6), Instant.now().getEpochSecond(),true, true, new HashSet<>());
 
         Map keysets = new HashMap() {{
             put(3, adminKeyset1);
@@ -1333,11 +1331,10 @@ void RelatedKeysetSetsWithAllowedSites(Vertx vertx, VertxTestContext testContext
     @Test
     void RelatedKeysetSetsWithSameSiteId(Vertx vertx, VertxTestContext testContext) {
         fakeAuth(Role.MAINTAINER);
-        when(clock.getEpochSecond()).thenReturn(1722938135L);
 
-        AdminKeyset adminKeyset1 = new AdminKeyset(3, 1, "test", Set.of(4), clock.getEpochSecond(),true, true, new HashSet<>());
-        AdminKeyset adminKeyset2 = new AdminKeyset(4, 2, "test", Set.of(5), clock.getEpochSecond(),true, true, new HashSet<>());
-        AdminKeyset adminKeyset3 = new AdminKeyset(5, 8, "test", Set.of(6), clock.getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset1 = new AdminKeyset(3, 1, "test", Set.of(4), Instant.now().getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset2 = new AdminKeyset(4, 2, "test", Set.of(5), Instant.now().getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset3 = new AdminKeyset(5, 8, "test", Set.of(6), Instant.now().getEpochSecond(),true, true, new HashSet<>());
 
         Map keysets = new HashMap() {{
             put(3, adminKeyset1);
@@ -1370,11 +1367,10 @@ void RelatedKeysetSetsWithSameSiteId(Vertx vertx, VertxTestContext testContext)
     @Test
     void RelatedKeysetSetsWithAllowSiteNull(Vertx vertx, VertxTestContext testContext) {
         fakeAuth(Role.MAINTAINER);
-        when(clock.getEpochSecond()).thenReturn(1722938135L);
 
-        AdminKeyset adminKeyset1 = new AdminKeyset(3, 1, "test", Set.of(4), clock.getEpochSecond(),true, true, new HashSet<>());
-        AdminKeyset adminKeyset2 = new AdminKeyset(4, 2, "test", Set.of(5), clock.getEpochSecond(),true, true, new HashSet<>());
-        AdminKeyset adminKeyset3 = new AdminKeyset(5, 3, "test", null, clock.getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset1 = new AdminKeyset(3, 1, "test", Set.of(4), Instant.now().getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset2 = new AdminKeyset(4, 2, "test", Set.of(5), Instant.now().getEpochSecond(),true, true, new HashSet<>());
+        AdminKeyset adminKeyset3 = new AdminKeyset(5, 3, "test", null, Instant.now().getEpochSecond(),true, true, new HashSet<>());
 
         Map keysets = new HashMap() {{
             put(3, adminKeyset1);

From 9875b7354f852efda917dee03d26c61a6548f587 Mon Sep 17 00:00:00 2001
From: Katherine Chen 
Date: Tue, 6 Aug 2024 22:04:56 +1000
Subject: [PATCH 16/23] Add changes to show rotation result

---
 webroot/adm/oncall/participant-summary.html |  4 ++--
 webroot/js/participantSummary.js            | 24 +++++++++++++++------
 2 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/webroot/adm/oncall/participant-summary.html b/webroot/adm/oncall/participant-summary.html
index df322b7d..766568ef 100644
--- a/webroot/adm/oncall/participant-summary.html
+++ b/webroot/adm/oncall/participant-summary.html
@@ -97,8 +97,8 @@ 
Participant Related Keysets
Rotate Keysets
Note: Once rotated, the participant will need to get a new refresh key to be able to decrypt data.
-

-                                

+                                

+                                

                             
diff --git a/webroot/js/participantSummary.js b/webroot/js/participantSummary.js index c4008063..31866420 100644 --- a/webroot/js/participantSummary.js +++ b/webroot/js/participantSummary.js @@ -10,6 +10,16 @@ function loadAllSitesCallback(result) { siteList = JSON.parse(result).map((item) => { return { name: item.name, id: item.id, clientTypes: item.clientTypes } }); }; +function rotateKeysetsCallback(result, keyset_id) { + const resultJson = JSON.parse(result); + const formatted = prettifyJson(JSON.stringify(resultJson)); + $('#rotateKeysetsStandardOutput').append(`keyset_id: ${keyset_id} rotated:
${formatted}
`); +} + +function rotateKeysetsErrorHandler(err, keyset_id) { + $('#rotateKeysetsErrorOutput').append(`keyset_id: ${keyset_id} rotation failed:
${err}
`); +} + function loadSiteCallback(result) { const resultJson = JSON.parse(result); @@ -92,14 +102,11 @@ function loadRelatedKeysetsCallback(result, siteId, clientTypes) { const resultJson = JSON.parse(result); resultJson.forEach(obj => { // Keysets where allowed_types include any of the clientTypes that belows to the site - console.log(obj.allowed_types); obj.allowed_types = obj.allowed_types.map(item => { return clientTypes.includes(item) ? `` : item; }); - console.log(obj.allowed_types); // Keysets where allowed_sites include the leaked site. As it's an integer object, change it to a placeholder and replace later. - console.log("kat"); if (obj.allowed_sites) { obj.allowed_sites = obj.allowed_sites.map(item => { return item === siteId ? "" : item; @@ -165,12 +172,15 @@ $(document).ready(() => { $('#doRotateKeysets').on('click', () => { var keysets = $('#relatedKeysetsStandardOutput').text(); - console.log(keysets); const ja = JSON.parse(keysets); + var rotateKeysetsMessage = ''; ja.forEach((keyset) => { - var url = `/api/key/rotate_keyset_key?min_age_seconds=0&keyset_id=${keyset.keyset_id}&force=true`; - - doApiCall('POST', url, '#standardOutput', '#errorOutput'); + var url = `/api/key/rotate_keyset_key?min_age_seconds=3600&keyset_id=${keyset.keyset_id}&force=true`; + doApiCallWithCallback( + 'POST', + url, + (r) => { rotateKeysetsCallback(r, keyset.keyset_id) }, + (err) => { rotateKeysetsErrorHandler(err, '#rotateKeysetsErrorOutput') }); }); }); }); \ No newline at end of file From b22494a7f20fc8c08dece880bf31e445d4c62ddb Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Wed, 7 Aug 2024 15:47:12 +1000 Subject: [PATCH 17/23] Update `min_age_seconds` to 1 --- webroot/js/participantSummary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webroot/js/participantSummary.js b/webroot/js/participantSummary.js index 31866420..aa044627 100644 --- a/webroot/js/participantSummary.js +++ b/webroot/js/participantSummary.js @@ -175,7 +175,7 @@ $(document).ready(() => { const ja = JSON.parse(keysets); var rotateKeysetsMessage = ''; ja.forEach((keyset) => { - var url = `/api/key/rotate_keyset_key?min_age_seconds=3600&keyset_id=${keyset.keyset_id}&force=true`; + var url = `/api/key/rotate_keyset_key?min_age_seconds=1&keyset_id=${keyset.keyset_id}&force=true`; doApiCallWithCallback( 'POST', url, From 6e0e979c326206091d62a7bb4c90098a636cb02c Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Wed, 7 Aug 2024 15:48:56 +1000 Subject: [PATCH 18/23] Use `Collections.disjoint` instead of customised `ContainAny` --- .../com/uid2/admin/vertx/service/SharingService.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/main/java/com/uid2/admin/vertx/service/SharingService.java b/src/main/java/com/uid2/admin/vertx/service/SharingService.java index 31044bce..46ad53cc 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SharingService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SharingService.java @@ -161,16 +161,6 @@ private void handleSetKeyset(RoutingContext rc) { } } - // Method to check if one set contains any values from another set - private static boolean containsAny(Set set1, Set set2) { - for (T element : set2) { - if (set1.contains(element)) { - return true; - } - } - return false; - } - private void handleListAllKeysetsRelated(RoutingContext rc) { try { // Get value for site id @@ -203,7 +193,7 @@ private void handleListAllKeysetsRelated(RoutingContext rc) { // b. If this participant has a client key with ID_READER role, we want to rotate all the keysets where allowed_sites is set to null // c. Keysets where allowed_sites include the leaked site // d. Keysets belonging to the leaked site itself - if (containsAny(keyset.getValue().getAllowedTypes(), clientTypes) || + if (Collections.disjoint(keyset.getValue().getAllowedTypes(), clientTypes) || isIdReaderRole && keyset.getValue().getAllowedSites() == null || keyset.getValue().getAllowedSites() != null && keyset.getValue().getAllowedSites().contains(siteId) || keyset.getValue().getSiteId() == siteId) { From bbc1a369ed64afb32dc70177321547e82fbd15c5 Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Wed, 7 Aug 2024 15:51:39 +1000 Subject: [PATCH 19/23] Modify logic for verifying site id --- .../java/com/uid2/admin/vertx/service/SharingService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/uid2/admin/vertx/service/SharingService.java b/src/main/java/com/uid2/admin/vertx/service/SharingService.java index 46ad53cc..28e509cc 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SharingService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SharingService.java @@ -165,10 +165,13 @@ private void handleListAllKeysetsRelated(RoutingContext rc) { try { // Get value for site id final Optional siteIdOpt = RequestUtil.getSiteId(rc, "site_id"); - if (!siteIdOpt.isPresent()) return; + if (!siteIdOpt.isPresent()) { + ResponseUtil.error(rc, 400, "must specify a site id"); + return; + } final int siteId = siteIdOpt.get(); - if (siteId != Const.Data.AdvertisingTokenSiteId && !SiteUtil.isValidSiteId(siteId)) { + if (!SiteUtil.isValidSiteId(siteId)) { ResponseUtil.error(rc, 400, "must specify a valid site id"); return; } From 5c6af7188f13f5cbcfaf19ca3aafbfa9fd7a9ce6 Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Thu, 8 Aug 2024 09:10:37 +1000 Subject: [PATCH 20/23] Add comments for moving checking keyset logic to shared --- .../java/com/uid2/admin/store/parser/AdminKeysetParser.java | 1 - .../java/com/uid2/admin/vertx/service/SharingService.java | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/uid2/admin/store/parser/AdminKeysetParser.java b/src/main/java/com/uid2/admin/store/parser/AdminKeysetParser.java index f57cdad9..fe896ff9 100644 --- a/src/main/java/com/uid2/admin/store/parser/AdminKeysetParser.java +++ b/src/main/java/com/uid2/admin/store/parser/AdminKeysetParser.java @@ -6,7 +6,6 @@ import com.uid2.shared.store.parser.Parser; import com.uid2.shared.store.parser.ParsingResult; import com.uid2.shared.Utils; -import com.uid2.shared.auth.KeysetSnapshot; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; diff --git a/src/main/java/com/uid2/admin/vertx/service/SharingService.java b/src/main/java/com/uid2/admin/vertx/service/SharingService.java index 28e509cc..9ffe10f3 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SharingService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SharingService.java @@ -10,6 +10,7 @@ import com.uid2.admin.managers.KeysetManager; import com.uid2.admin.vertx.ResponseUtil; import com.uid2.shared.Const; +import com.uid2.shared.auth.KeysetSnapshot; import com.uid2.shared.auth.Role; import com.uid2.shared.model.ClientType; import com.uid2.shared.model.SiteUtil; @@ -26,8 +27,6 @@ import java.util.*; import java.util.stream.Collectors; -import static com.uid2.admin.vertx.RequestUtil.getTypes; - public class SharingService implements IService { private final AdminAuthMiddleware auth; @@ -200,6 +199,8 @@ private void handleListAllKeysetsRelated(RoutingContext rc) { isIdReaderRole && keyset.getValue().getAllowedSites() == null || keyset.getValue().getAllowedSites() != null && keyset.getValue().getAllowedSites().contains(siteId) || keyset.getValue().getSiteId() == siteId) { + // TODO: We have functions below which check if a keysetkey is accessible by a client. We should move the logic of checking keyset to shared as well. + // https://github.com/IABTechLab/uid2-shared/blob/19edb010c6a4d753d03c89268c238be10a8f6722/src/main/java/com/uid2/shared/auth/KeysetSnapshot.java#L13 ja.add(jsonFullKeyset(keyset.getValue())); } } From d06b81bbba1a10bd927546568905bc61d44592b5 Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Thu, 8 Aug 2024 09:26:34 +1000 Subject: [PATCH 21/23] Fix logic of disjoint --- src/main/java/com/uid2/admin/vertx/service/SharingService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/uid2/admin/vertx/service/SharingService.java b/src/main/java/com/uid2/admin/vertx/service/SharingService.java index 9ffe10f3..6fd6466d 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SharingService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SharingService.java @@ -195,7 +195,7 @@ private void handleListAllKeysetsRelated(RoutingContext rc) { // b. If this participant has a client key with ID_READER role, we want to rotate all the keysets where allowed_sites is set to null // c. Keysets where allowed_sites include the leaked site // d. Keysets belonging to the leaked site itself - if (Collections.disjoint(keyset.getValue().getAllowedTypes(), clientTypes) || + if (!Collections.disjoint(keyset.getValue().getAllowedTypes(), clientTypes) || isIdReaderRole && keyset.getValue().getAllowedSites() == null || keyset.getValue().getAllowedSites() != null && keyset.getValue().getAllowedSites().contains(siteId) || keyset.getValue().getSiteId() == siteId) { From 49d0971438f66f493d654a507dce9db824920972 Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Thu, 8 Aug 2024 10:44:23 +1000 Subject: [PATCH 22/23] Add prompt to confirm rotation --- webroot/js/participantSummary.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webroot/js/participantSummary.js b/webroot/js/participantSummary.js index aa044627..adc7467f 100644 --- a/webroot/js/participantSummary.js +++ b/webroot/js/participantSummary.js @@ -171,6 +171,10 @@ $(document).ready(() => { }); $('#doRotateKeysets').on('click', () => { + if (!confirm("Are you sure?")) { + return; + } + var keysets = $('#relatedKeysetsStandardOutput').text(); const ja = JSON.parse(keysets); var rotateKeysetsMessage = ''; From 9a2026346fe4ece1b2c6475c639ee07e3988e9e5 Mon Sep 17 00:00:00 2001 From: Katherine Chen Date: Thu, 8 Aug 2024 13:23:10 +1000 Subject: [PATCH 23/23] Update wordings for the note --- webroot/adm/oncall/participant-summary.html | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/webroot/adm/oncall/participant-summary.html b/webroot/adm/oncall/participant-summary.html index 766568ef..e3067322 100644 --- a/webroot/adm/oncall/participant-summary.html +++ b/webroot/adm/oncall/participant-summary.html @@ -95,7 +95,12 @@
Participant Related Keysets
Rotate Keysets -
Note: Once rotated, the participant will need to get a new refresh key to be able to decrypt data.
+
+ Normally, keys don't become active for 24 hours when rotated, which gives participants 24 hours before they need to call sdk.refresh(). + However in this case, rotation will make the new keys active immediately. + This means participants will not be able to decrypt newly created UID tokens until they have called sdk.refresh(). + Note that we recommend calling sdk.refresh() once per hour (see documentation) +