From 28a0fd8d0b55d64ce0f070239f9b002b3a5bd2b5 Mon Sep 17 00:00:00 2001 From: Jordan Padams Date: Thu, 27 Aug 2026 09:15:00 -0700 Subject: [PATCH 1/2] Update git ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index fe61f0a..d96f906 100644 --- a/.gitignore +++ b/.gitignore @@ -147,3 +147,4 @@ typescript !.gitattributes !.gitignore !.gitkeep +.envrc From c0444528f4652340aa291dc835b6048f6a72bee9 Mon Sep 17 00:00:00 2001 From: Jordan Padams Date: Fri, 28 Aug 2026 08:06:34 -0700 Subject: [PATCH 2/2] Support multiple Cognito user groups per node ID to fix SBN 401 errors Replaces auto-generated single group names with an explicit node_id_to_cognito_groups map in NodeUtil. SBN now maps to both PDS_SBN_USERS and PDS_SBNUMD_USERS. All client callsites send a comma-separated UserGroup header; the Lambda authorizer authorizes if the user belongs to any group in the list. Fixes #384 Co-Authored-By: Claude Sonnet 4.6 --- src/pds/ingress/authorizer/index.js | 9 +++++++-- src/pds/ingress/client/pds_ingress_client.py | 2 +- src/pds/ingress/client/pds_status_client.py | 4 ++-- src/pds/ingress/util/log_util.py | 4 ++-- src/pds/ingress/util/node_util.py | 21 ++++++++++++++++---- tests/pds/ingress/util/test_log_util.py | 2 +- 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/pds/ingress/authorizer/index.js b/src/pds/ingress/authorizer/index.js index 8afaf20..856f846 100644 --- a/src/pds/ingress/authorizer/index.js +++ b/src/pds/ingress/authorizer/index.js @@ -60,11 +60,16 @@ exports.handler = async(event, _context, callback) => { return; } - if (groups.includes(request_group)) { + // The UserGroup header may contain multiple comma-separated group names (e.g. for SBN which + // has two orgs). Authorize if the user belongs to ANY of the permitted groups for the node. + let permitted_groups = request_group.split(',').map(g => g.trim()); + let authorized = permitted_groups.some(g => groups.includes(g)); + + if (authorized) { console.log("VALID TOKEN, ALLOW!!"); callback(null, generatePolicy('user', 'Allow', event.methodArn)); } else { - console.log(`Invalid request group. User belongs to: [${groups}], but requested: ${request_group}`); + console.log(`Invalid request group. User belongs to: [${groups}], but requested: [${permitted_groups}]`); callback("Unauthorized"); } }; diff --git a/src/pds/ingress/client/pds_ingress_client.py b/src/pds/ingress/client/pds_ingress_client.py index 57c54d7..e3b067c 100644 --- a/src/pds/ingress/client/pds_ingress_client.py +++ b/src/pds/ingress/client/pds_ingress_client.py @@ -457,7 +457,7 @@ def request_batch_for_ingress(request_batch, batch_index, node_id, force_overwri params = {"node": node_id, "node_name": NodeUtil.node_id_to_long_name[node_id]} headers = { "Authorization": BEARER_TOKEN, - "UserGroup": NodeUtil.node_id_to_group_name(node_id), + "UserGroup": ",".join(NodeUtil.node_id_to_group_names(node_id)), "ForceOverwrite": str(int(force_overwrite)), "ClientVersion": __version__, "content-type": "application/json", diff --git a/src/pds/ingress/client/pds_status_client.py b/src/pds/ingress/client/pds_status_client.py index 847530c..d509ae2 100644 --- a/src/pds/ingress/client/pds_status_client.py +++ b/src/pds/ingress/client/pds_status_client.py @@ -143,7 +143,7 @@ def main(args): params = {"node": args.node, "node_name": NodeUtil.node_id_to_long_name[args.node]} headers = { "Authorization": bearer_token, - "UserGroup": NodeUtil.node_id_to_group_name(args.node), + "UserGroup": ",".join(NodeUtil.node_id_to_group_names(args.node)), "ForceOverwrite": "1", # Likely we'll repeat requests using same manifest file names, so always overwrite "ClientVersion": __version__, "content-type": "application/json", @@ -184,7 +184,7 @@ def main(args): headers = { "Authorization": bearer_token, - "UserGroup": NodeUtil.node_id_to_group_name(args.node), + "UserGroup": ",".join(NodeUtil.node_id_to_group_names(args.node)), "ClientVersion": __version__, "content-type": "application/json", "x-amz-docs-region": api_gateway_region, diff --git a/src/pds/ingress/util/log_util.py b/src/pds/ingress/util/log_util.py index c062b94..f02a46b 100644 --- a/src/pds/ingress/util/log_util.py +++ b/src/pds/ingress/util/log_util.py @@ -501,7 +501,7 @@ def send_log_events_to_cloud_watch(self, log_events): headers = { "Authorization": self.bearer_token, - "UserGroup": NodeUtil.node_id_to_group_name(self.node_id), + "UserGroup": ",".join(NodeUtil.node_id_to_group_names(self.node_id)), "content-type": "application/json", "x-amz-docs-region": api_gateway_region, } @@ -522,7 +522,7 @@ def send_log_events_to_cloud_watch(self, log_events): payload["logEvents"] = log_events headers = { "Authorization": self.bearer_token, - "UserGroup": NodeUtil.node_id_to_group_name(self.node_id), + "UserGroup": ",".join(NodeUtil.node_id_to_group_names(self.node_id)), "content-type": "application/json", "x-amz-docs-region": api_gateway_region, } diff --git a/src/pds/ingress/util/node_util.py b/src/pds/ingress/util/node_util.py index 307e44d..82be73e 100644 --- a/src/pds/ingress/util/node_util.py +++ b/src/pds/ingress/util/node_util.py @@ -23,15 +23,28 @@ class NodeUtil: "sbn": "Small Bodies", } + node_id_to_cognito_groups = { + "atm": ["PDS_ATM_USERS"], + "eng": ["PDS_ENG_USERS"], + "geo": ["PDS_GEO_USERS"], + "img": ["PDS_IMG_USERS"], + "naif": ["PDS_NAIF_USERS"], + "ppi": ["PDS_PPI_USERS"], + "rs": ["PDS_RS_USERS"], + "rms": ["PDS_RMS_USERS"], + "sbn": ["PDS_SBN_USERS", "PDS_SBNUMD_USERS"], + } + @classmethod def permissible_node_ids(cls): """Returns a list of the Node IDs accepted by the Ingress client""" return cls.node_id_to_long_name.keys() @classmethod - def node_id_to_group_name(cls, node_id): - """Returns the Cognito group name for the given node ID""" - if node_id.lower() not in cls.node_id_to_long_name.keys(): + def node_id_to_group_names(cls, node_id): + """Returns all Cognito group names permitted for the given node ID""" + if node_id.lower() not in cls.node_id_to_cognito_groups: raise ValueError(f'Unknown node ID "{node_id}"') - return f"PDS_{node_id.upper()}_USERS" + return cls.node_id_to_cognito_groups[node_id.lower()] + diff --git a/tests/pds/ingress/util/test_log_util.py b/tests/pds/ingress/util/test_log_util.py index 61f7ba5..7662e40 100644 --- a/tests/pds/ingress/util/test_log_util.py +++ b/tests/pds/ingress/util/test_log_util.py @@ -131,7 +131,7 @@ def requests_post_patch(url, data=None, json=None, **kwargs): self.assertIn("Authorization", headers) self.assertIn("UserGroup", headers) self.assertEqual(headers["Authorization"], "Bearer faketoken") - self.assertEqual(headers["UserGroup"], NodeUtil.node_id_to_group_name("eng")) + self.assertEqual(headers["UserGroup"], ",".join(NodeUtil.node_id_to_group_names("eng"))) response = requests.Response() response.status_code = HTTPStatus.OK