From 1fa7c55568107a0b0d327f2aac05afb3ad9a96fb Mon Sep 17 00:00:00 2001 From: Pierre Bastianelli Date: Tue, 10 Jan 2023 16:04:27 -0800 Subject: [PATCH 01/35] refactor: jwt_token type has a sub of type text --- schema/deploy/types/jwt_token.sql | 26 ++---------------- schema/deploy/types/jwt_token@v2.18.0.sql | 32 +++++++++++++++++++++++ schema/revert/types/jwt_token.sql | 29 ++++++++++++++++++-- schema/revert/types/jwt_token@v2.18.0.sql | 7 +++++ schema/sqitch.plan | 1 + schema/verify/types/jwt_token@v2.18.0.sql | 13 +++++++++ 6 files changed, 82 insertions(+), 26 deletions(-) create mode 100644 schema/deploy/types/jwt_token@v2.18.0.sql create mode 100644 schema/revert/types/jwt_token@v2.18.0.sql create mode 100644 schema/verify/types/jwt_token@v2.18.0.sql diff --git a/schema/deploy/types/jwt_token.sql b/schema/deploy/types/jwt_token.sql index 234fcfcb55..686df09ae7 100644 --- a/schema/deploy/types/jwt_token.sql +++ b/schema/deploy/types/jwt_token.sql @@ -3,30 +3,8 @@ begin; -create type ggircs_portal.jwt_token as ( - jti uuid, - exp integer, - nbf integer, - iat integer, - iss text, - aud text, - sub uuid, - typ text, - azp text, - auth_time integer, - session_state uuid, - acr text, - email_verified boolean, - name text, - preferred_username text, - given_name text, - family_name text, - email text, - broker_session_id text, - priority_group text, - user_groups text[] -); +alter type ggircs_portal.jwt_token alter attribute sub type text; -comment on type ggircs_portal.jwt_token is E'@primaryKey sub\n@foreignKey (sub) references ciip_user (uuid)'; +comment on type ggircs_portal.jwt_token is E'@primaryKey sub\n@foreignKey (sub) references ciip_user (session_sub)'; commit; diff --git a/schema/deploy/types/jwt_token@v2.18.0.sql b/schema/deploy/types/jwt_token@v2.18.0.sql new file mode 100644 index 0000000000..234fcfcb55 --- /dev/null +++ b/schema/deploy/types/jwt_token@v2.18.0.sql @@ -0,0 +1,32 @@ +-- Deploy ggircs-portal:type_jwt_token to pg +-- requires: schema_ggircs_portal + +begin; + +create type ggircs_portal.jwt_token as ( + jti uuid, + exp integer, + nbf integer, + iat integer, + iss text, + aud text, + sub uuid, + typ text, + azp text, + auth_time integer, + session_state uuid, + acr text, + email_verified boolean, + name text, + preferred_username text, + given_name text, + family_name text, + email text, + broker_session_id text, + priority_group text, + user_groups text[] +); + +comment on type ggircs_portal.jwt_token is E'@primaryKey sub\n@foreignKey (sub) references ciip_user (uuid)'; + +commit; diff --git a/schema/revert/types/jwt_token.sql b/schema/revert/types/jwt_token.sql index 9f9a10cfbb..234fcfcb55 100644 --- a/schema/revert/types/jwt_token.sql +++ b/schema/revert/types/jwt_token.sql @@ -1,7 +1,32 @@ --- Revert ggircs-portal:type_jwt_token from pg +-- Deploy ggircs-portal:type_jwt_token to pg +-- requires: schema_ggircs_portal begin; -drop type ggircs_portal.jwt_token; +create type ggircs_portal.jwt_token as ( + jti uuid, + exp integer, + nbf integer, + iat integer, + iss text, + aud text, + sub uuid, + typ text, + azp text, + auth_time integer, + session_state uuid, + acr text, + email_verified boolean, + name text, + preferred_username text, + given_name text, + family_name text, + email text, + broker_session_id text, + priority_group text, + user_groups text[] +); + +comment on type ggircs_portal.jwt_token is E'@primaryKey sub\n@foreignKey (sub) references ciip_user (uuid)'; commit; diff --git a/schema/revert/types/jwt_token@v2.18.0.sql b/schema/revert/types/jwt_token@v2.18.0.sql new file mode 100644 index 0000000000..9f9a10cfbb --- /dev/null +++ b/schema/revert/types/jwt_token@v2.18.0.sql @@ -0,0 +1,7 @@ +-- Revert ggircs-portal:type_jwt_token from pg + +begin; + +drop type ggircs_portal.jwt_token; + +commit; diff --git a/schema/sqitch.plan b/schema/sqitch.plan index 1cda1b0578..8e50e5b334 100644 --- a/schema/sqitch.plan +++ b/schema/sqitch.plan @@ -416,3 +416,4 @@ computed_columns/facility_application_has_swrs_report [queries/facility_applicat @v2.18.4 2022-11-04T20:58:09Z Dylan Leard # release v2.18.4 @v2.18.5 2022-11-21T17:08:07Z Dylan Leard # release v2.18.5 @v2.18.6 2022-11-22T23:04:34Z Dylan Leard # release v2.18.6 +types/jwt_token [types/jwt_token@v2.18.0] 2023-01-11T00:01:27Z Pierre Bastianelli # Updating the jwt_token to have a sub of type text diff --git a/schema/verify/types/jwt_token@v2.18.0.sql b/schema/verify/types/jwt_token@v2.18.0.sql new file mode 100644 index 0000000000..320e98dd26 --- /dev/null +++ b/schema/verify/types/jwt_token@v2.18.0.sql @@ -0,0 +1,13 @@ +-- Verify ggircs-portal:type_jwt_token on pg + +begin; + +do $$ + begin + assert ( + select true from pg_catalog.pg_type where typname = 'jwt_token' + ), 'type "jwt_token" is not defined'; + end; +$$; + +rollback; From 7173bb3dc7c5db958b32a0cfad699a1b71d69371 Mon Sep 17 00:00:00 2001 From: Pierre Bastianelli Date: Tue, 10 Jan 2023 16:11:40 -0800 Subject: [PATCH 02/35] refactor: trigger to disallow user update under certain conditions --- .../user_session_sub_immutable_with_flag.sql | 23 +++++++++++++++++++ .../user_session_sub_immutable_with_flag.sql | 7 ++++++ schema/sqitch.plan | 1 + .../user_session_sub_immutable_with_flag.sql | 7 ++++++ 4 files changed, 38 insertions(+) create mode 100644 schema/deploy/trigger_functions/user_session_sub_immutable_with_flag.sql create mode 100644 schema/revert/trigger_functions/user_session_sub_immutable_with_flag.sql create mode 100644 schema/verify/trigger_functions/user_session_sub_immutable_with_flag.sql diff --git a/schema/deploy/trigger_functions/user_session_sub_immutable_with_flag.sql b/schema/deploy/trigger_functions/user_session_sub_immutable_with_flag.sql new file mode 100644 index 0000000000..347ee52300 --- /dev/null +++ b/schema/deploy/trigger_functions/user_session_sub_immutable_with_flag.sql @@ -0,0 +1,23 @@ +-- Deploy ggircs-portal:trigger_functions/user_session_sub_immutable_with_flag to pg + + +begin; + +create or replace function ggircs_portal_private.user_session_sub_immutable_with_flag_set() +returns trigger as $$ +begin + if not old.allow_sub_update then + raise exception 'session_sub cannot be updated when allow_sub_update is false'; + end if; + return new; +end; +$$ language plpgsql; + +grant execute on function ggircs_portal_private.user_session_sub_immutable_with_flag_set to ciip_administrator, ciip_analyst, ciip_industry_user; + +comment on function ggircs_portal_private.user_session_sub_immutable_with_flag_set() + is $$ + A trigger that raises an exception if the session_sub of a user is being updated with the allow_sub_update flag set to false + $$; + +commit; diff --git a/schema/revert/trigger_functions/user_session_sub_immutable_with_flag.sql b/schema/revert/trigger_functions/user_session_sub_immutable_with_flag.sql new file mode 100644 index 0000000000..fbb5550f7a --- /dev/null +++ b/schema/revert/trigger_functions/user_session_sub_immutable_with_flag.sql @@ -0,0 +1,7 @@ +-- Revert ggircs-portal:trigger_functions/user_session_sub_immutable_with_flag from pg + +begin; + +function ggircs_portal_private.user_session_sub_immutable_with_flag_set; + +commit; diff --git a/schema/sqitch.plan b/schema/sqitch.plan index 8e50e5b334..9568ceee97 100644 --- a/schema/sqitch.plan +++ b/schema/sqitch.plan @@ -417,3 +417,4 @@ computed_columns/facility_application_has_swrs_report [queries/facility_applicat @v2.18.5 2022-11-21T17:08:07Z Dylan Leard # release v2.18.5 @v2.18.6 2022-11-22T23:04:34Z Dylan Leard # release v2.18.6 types/jwt_token [types/jwt_token@v2.18.0] 2023-01-11T00:01:27Z Pierre Bastianelli # Updating the jwt_token to have a sub of type text +trigger_functions/user_session_sub_immutable_with_flag 2023-01-11T00:07:27Z Pierre Bastianelli # Trigger that throws on update if the allow_sub_update flag is set to false diff --git a/schema/verify/trigger_functions/user_session_sub_immutable_with_flag.sql b/schema/verify/trigger_functions/user_session_sub_immutable_with_flag.sql new file mode 100644 index 0000000000..34d26a9883 --- /dev/null +++ b/schema/verify/trigger_functions/user_session_sub_immutable_with_flag.sql @@ -0,0 +1,7 @@ +-- Verify ggircs-portal:trigger_functions/user_session_sub_immutable_with_flag on pg + +begin; + +select pg_get_functiondef('ggircs_portal_private.user_session_sub_immutable_with_flag_set()'::regprocedure); + +rollback; From 3b27c65d45bd4ce1e47a0df947820e8619006962 Mon Sep 17 00:00:00 2001 From: Pierre Bastianelli Date: Tue, 10 Jan 2023 16:19:51 -0800 Subject: [PATCH 03/35] refactor: revert functions --- .../user_session_sub_immutable_with_flag.sql | 2 +- schema/revert/types/jwt_token.sql | 24 +------------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/schema/revert/trigger_functions/user_session_sub_immutable_with_flag.sql b/schema/revert/trigger_functions/user_session_sub_immutable_with_flag.sql index fbb5550f7a..5c591f028b 100644 --- a/schema/revert/trigger_functions/user_session_sub_immutable_with_flag.sql +++ b/schema/revert/trigger_functions/user_session_sub_immutable_with_flag.sql @@ -2,6 +2,6 @@ begin; -function ggircs_portal_private.user_session_sub_immutable_with_flag_set; +drop function ggircs_portal_private.user_session_sub_immutable_with_flag_set; commit; diff --git a/schema/revert/types/jwt_token.sql b/schema/revert/types/jwt_token.sql index 234fcfcb55..a33344a893 100644 --- a/schema/revert/types/jwt_token.sql +++ b/schema/revert/types/jwt_token.sql @@ -3,29 +3,7 @@ begin; -create type ggircs_portal.jwt_token as ( - jti uuid, - exp integer, - nbf integer, - iat integer, - iss text, - aud text, - sub uuid, - typ text, - azp text, - auth_time integer, - session_state uuid, - acr text, - email_verified boolean, - name text, - preferred_username text, - given_name text, - family_name text, - email text, - broker_session_id text, - priority_group text, - user_groups text[] -); +alter type ggircs_portal.jwt_token alter attribute sub type uuid; comment on type ggircs_portal.jwt_token is E'@primaryKey sub\n@foreignKey (sub) references ciip_user (uuid)'; From 0ab3acfaa82c59d39fc78faa4bf52d2765b0e406 Mon Sep 17 00:00:00 2001 From: Pierre Bastianelli Date: Wed, 11 Jan 2023 16:46:58 -0800 Subject: [PATCH 04/35] refactor: rename immutable flag function --- .../user_session_sub_immutable_with_flag.sql | 23 ------------------- .../user_uuid_immutable_with_flag.sql | 23 +++++++++++++++++++ .../user_session_sub_immutable_with_flag.sql | 7 ------ .../user_uuid_immutable_with_flag.sql | 7 ++++++ .../user_session_sub_immutable_with_flag.sql | 7 ------ .../user_uuid_immutable_with_flag.sql | 7 ++++++ 6 files changed, 37 insertions(+), 37 deletions(-) delete mode 100644 schema/deploy/trigger_functions/user_session_sub_immutable_with_flag.sql create mode 100644 schema/deploy/trigger_functions/user_uuid_immutable_with_flag.sql delete mode 100644 schema/revert/trigger_functions/user_session_sub_immutable_with_flag.sql create mode 100644 schema/revert/trigger_functions/user_uuid_immutable_with_flag.sql delete mode 100644 schema/verify/trigger_functions/user_session_sub_immutable_with_flag.sql create mode 100644 schema/verify/trigger_functions/user_uuid_immutable_with_flag.sql diff --git a/schema/deploy/trigger_functions/user_session_sub_immutable_with_flag.sql b/schema/deploy/trigger_functions/user_session_sub_immutable_with_flag.sql deleted file mode 100644 index 347ee52300..0000000000 --- a/schema/deploy/trigger_functions/user_session_sub_immutable_with_flag.sql +++ /dev/null @@ -1,23 +0,0 @@ --- Deploy ggircs-portal:trigger_functions/user_session_sub_immutable_with_flag to pg - - -begin; - -create or replace function ggircs_portal_private.user_session_sub_immutable_with_flag_set() -returns trigger as $$ -begin - if not old.allow_sub_update then - raise exception 'session_sub cannot be updated when allow_sub_update is false'; - end if; - return new; -end; -$$ language plpgsql; - -grant execute on function ggircs_portal_private.user_session_sub_immutable_with_flag_set to ciip_administrator, ciip_analyst, ciip_industry_user; - -comment on function ggircs_portal_private.user_session_sub_immutable_with_flag_set() - is $$ - A trigger that raises an exception if the session_sub of a user is being updated with the allow_sub_update flag set to false - $$; - -commit; diff --git a/schema/deploy/trigger_functions/user_uuid_immutable_with_flag.sql b/schema/deploy/trigger_functions/user_uuid_immutable_with_flag.sql new file mode 100644 index 0000000000..da96cfd67f --- /dev/null +++ b/schema/deploy/trigger_functions/user_uuid_immutable_with_flag.sql @@ -0,0 +1,23 @@ +-- Deploy ggircs-portal:trigger_functions/user_uuid_immutable_with_flag to pg + + +begin; + +create or replace function ggircs_portal_private.user_uuid_immutable_with_flag_set() +returns trigger as $$ +begin + if not old.allow_sub_update then + raise exception 'uuid cannot be updated when allow_sub_update is false'; + end if; + return new; +end; +$$ language plpgsql; + +grant execute on function ggircs_portal_private.user_uuid_immutable_with_flag_set to ciip_administrator, ciip_analyst, ciip_industry_user; + +comment on function ggircs_portal_private.user_uuid_immutable_with_flag_set() + is $$ + A trigger that raises an exception if the uuid of a user is being updated with the allow_sub_update flag set to false + $$; + +commit; diff --git a/schema/revert/trigger_functions/user_session_sub_immutable_with_flag.sql b/schema/revert/trigger_functions/user_session_sub_immutable_with_flag.sql deleted file mode 100644 index 5c591f028b..0000000000 --- a/schema/revert/trigger_functions/user_session_sub_immutable_with_flag.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert ggircs-portal:trigger_functions/user_session_sub_immutable_with_flag from pg - -begin; - -drop function ggircs_portal_private.user_session_sub_immutable_with_flag_set; - -commit; diff --git a/schema/revert/trigger_functions/user_uuid_immutable_with_flag.sql b/schema/revert/trigger_functions/user_uuid_immutable_with_flag.sql new file mode 100644 index 0000000000..e558f89795 --- /dev/null +++ b/schema/revert/trigger_functions/user_uuid_immutable_with_flag.sql @@ -0,0 +1,7 @@ +-- Revert ggircs-portal:trigger_functions/user_uuid_immutable_with_flag from pg + +begin; + +drop function ggircs_portal_private.user_uuid_immutable_with_flag_set; + +commit; diff --git a/schema/verify/trigger_functions/user_session_sub_immutable_with_flag.sql b/schema/verify/trigger_functions/user_session_sub_immutable_with_flag.sql deleted file mode 100644 index 34d26a9883..0000000000 --- a/schema/verify/trigger_functions/user_session_sub_immutable_with_flag.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify ggircs-portal:trigger_functions/user_session_sub_immutable_with_flag on pg - -begin; - -select pg_get_functiondef('ggircs_portal_private.user_session_sub_immutable_with_flag_set()'::regprocedure); - -rollback; diff --git a/schema/verify/trigger_functions/user_uuid_immutable_with_flag.sql b/schema/verify/trigger_functions/user_uuid_immutable_with_flag.sql new file mode 100644 index 0000000000..27d7f418ac --- /dev/null +++ b/schema/verify/trigger_functions/user_uuid_immutable_with_flag.sql @@ -0,0 +1,7 @@ +-- Verify ggircs-portal:trigger_functions/user_uuid_immutable_with_flag on pg + +begin; + +select pg_get_functiondef('ggircs_portal_private.user_uuid_immutable_with_flag_set()'::regprocedure); + +rollback; From 5c1b55a792f9eaca510672c8cf672cc8a78770b7 Mon Sep 17 00:00:00 2001 From: Pierre Bastianelli Date: Wed, 11 Jan 2023 17:12:10 -0800 Subject: [PATCH 05/35] refactor: dropping policies before changing the uuid type --- ...p_policies_001_drop_before_type_change.sql | 8 +++++ ...n_policies_001_drop_before_type_change.sql | 9 ++++++ ...r_policies_001_drop_before_type_change.sql | 11 +++++++ ...p_policies_001_drop_before_type_change.sql | 31 +++++++++++++++++++ ...n_policies_001_drop_before_type_change.sql | 22 +++++++++++++ ...r_policies_001_drop_before_type_change.sql | 23 ++++++++++++++ ...p_policies_001_drop_before_type_change.sql | 7 +++++ ...n_policies_001_drop_before_type_change.sql | 8 +++++ ...r_policies_001_drop_before_type_change.sql | 11 +++++++ 9 files changed, 130 insertions(+) create mode 100644 schema/deploy/policies/application_review_step_policies_001_drop_before_type_change.sql create mode 100644 schema/deploy/policies/ciip_user_organisation_policies_001_drop_before_type_change.sql create mode 100644 schema/deploy/policies/ciip_user_policies_001_drop_before_type_change.sql create mode 100644 schema/revert/policies/application_review_step_policies_001_drop_before_type_change.sql create mode 100644 schema/revert/policies/ciip_user_organisation_policies_001_drop_before_type_change.sql create mode 100644 schema/revert/policies/ciip_user_policies_001_drop_before_type_change.sql create mode 100644 schema/verify/policies/application_review_step_policies_001_drop_before_type_change.sql create mode 100644 schema/verify/policies/ciip_user_organisation_policies_001_drop_before_type_change.sql create mode 100644 schema/verify/policies/ciip_user_policies_001_drop_before_type_change.sql diff --git a/schema/deploy/policies/application_review_step_policies_001_drop_before_type_change.sql b/schema/deploy/policies/application_review_step_policies_001_drop_before_type_change.sql new file mode 100644 index 0000000000..ab44875be3 --- /dev/null +++ b/schema/deploy/policies/application_review_step_policies_001_drop_before_type_change.sql @@ -0,0 +1,8 @@ +-- Deploy ggircs-portal:policies/application_review_step_policies_001_drop_before_type_change to pg + +begin; + +drop policy ciip_industry_user_select_application_review_step on ggircs_portal.application_review_step; + +commit; + diff --git a/schema/deploy/policies/ciip_user_organisation_policies_001_drop_before_type_change.sql b/schema/deploy/policies/ciip_user_organisation_policies_001_drop_before_type_change.sql new file mode 100644 index 0000000000..196ea0c2e8 --- /dev/null +++ b/schema/deploy/policies/ciip_user_organisation_policies_001_drop_before_type_change.sql @@ -0,0 +1,9 @@ +-- Deploy ggircs-portal:policies/ciip_user_organisation_policies_001_drop_before_type_change to pg + +begin; + +drop policy ciip_industry_user_select_ciip_user_organisation on ggircs_portal.ciip_user_organisation; +drop policy ciip_industry_user_insert_ciip_user_organisation on ggircs_portal.ciip_user_organisation; + +commit; + diff --git a/schema/deploy/policies/ciip_user_policies_001_drop_before_type_change.sql b/schema/deploy/policies/ciip_user_policies_001_drop_before_type_change.sql new file mode 100644 index 0000000000..bff2b892f6 --- /dev/null +++ b/schema/deploy/policies/ciip_user_policies_001_drop_before_type_change.sql @@ -0,0 +1,11 @@ +-- Deploy ggircs-portal:policies/ciip_user_policies_001_drop_before_type_change to pg + +begin; + +drop policy ciip_analyst_insert_ciip_user on ggircs_portal.ciip_user; +drop policy ciip_analyst_update_ciip_user on ggircs_portal.ciip_user; +drop policy ciip_industry_user_insert_ciip_user on ggircs_portal.ciip_user; +drop policy ciip_industry_user_update_ciip_user on ggircs_portal.ciip_user; +drop policy ciip_guest_select_ciip_user on ggircs_portal.ciip_user; + +commit; diff --git a/schema/revert/policies/application_review_step_policies_001_drop_before_type_change.sql b/schema/revert/policies/application_review_step_policies_001_drop_before_type_change.sql new file mode 100644 index 0000000000..aedaf9e2cc --- /dev/null +++ b/schema/revert/policies/application_review_step_policies_001_drop_before_type_change.sql @@ -0,0 +1,31 @@ +-- Revert ggircs-portal:policies/application_review_step_policies_001_drop_before_type_change from pg + +begin; + +do +$policy$ +declare + industry_user_statement text; + +begin + +industry_user_statement = $$ + application_id in ( + select a.id from ggircs_portal.application a + join ggircs_portal.facility f + on a.facility_id = f.id + join ggircs_portal.ciip_user_organisation cuo + on f.organisation_id = cuo.organisation_id + join ggircs_portal.ciip_user cu + on cuo.user_id = cu.id + and cu.uuid = (select sub from ggircs_portal.session()) + ) +$$; + +perform ggircs_portal_private.upsert_policy('ciip_industry_user_select_application_review_step', 'application_review_step', 'select', 'ciip_industry_user', industry_user_statement); + +end +$policy$; + + +commit; \ No newline at end of file diff --git a/schema/revert/policies/ciip_user_organisation_policies_001_drop_before_type_change.sql b/schema/revert/policies/ciip_user_organisation_policies_001_drop_before_type_change.sql new file mode 100644 index 0000000000..e0542ea830 --- /dev/null +++ b/schema/revert/policies/ciip_user_organisation_policies_001_drop_before_type_change.sql @@ -0,0 +1,22 @@ +-- Revert ggircs-portal:policies/ciip_user_organisation_policies_001_drop_before_type_change from pg + +begin; + +do + $policy$ + begin + + -- ciip_industry_user RLS + perform ggircs_portal_private.upsert_policy( + 'ciip_industry_user_select_ciip_user_organisation', + 'ciip_user_organisation', + 'select', + 'ciip_industry_user', + $$user_id=(select id from ggircs_portal.ciip_user where uuid = (select sub from ggircs_portal.session()))$$ + ); + perform ggircs_portal_private.upsert_policy('ciip_industry_user_insert_ciip_user_organisation', 'ciip_user_organisation', 'insert', 'ciip_industry_user', $$user_id=(select id from ggircs_portal.ciip_user where uuid = (select sub from ggircs_portal.session())) and status='pending'$$); + + end + $policy$; + +commit; diff --git a/schema/revert/policies/ciip_user_policies_001_drop_before_type_change.sql b/schema/revert/policies/ciip_user_policies_001_drop_before_type_change.sql new file mode 100644 index 0000000000..3f573cb9e5 --- /dev/null +++ b/schema/revert/policies/ciip_user_policies_001_drop_before_type_change.sql @@ -0,0 +1,23 @@ +-- Revert ggircs-portal:policies/ciip_user_policies_001_drop_before_type_change from pg + +begin; + +do +$policy$ +begin + +-- ciip_analyst RLS +perform ggircs_portal_private.upsert_policy('ciip_analyst_insert_ciip_user', 'ciip_user', 'insert', 'ciip_analyst', 'uuid=(select sub from ggircs_portal.session())'); +perform ggircs_portal_private.upsert_policy('ciip_analyst_update_ciip_user', 'ciip_user', 'update', 'ciip_analyst', 'uuid=(select sub from ggircs_portal.session())'); + +-- ciip_industry_user RLS +perform ggircs_portal_private.upsert_policy('ciip_industry_user_insert_ciip_user', 'ciip_user', 'insert', 'ciip_industry_user', 'uuid=(select sub from ggircs_portal.session())'); +perform ggircs_portal_private.upsert_policy('ciip_industry_user_update_ciip_user', 'ciip_user', 'update', 'ciip_industry_user', 'uuid=(select sub from ggircs_portal.session())'); + +-- ciip_guest RLS +perform ggircs_portal_private.upsert_policy('ciip_guest_select_ciip_user', 'ciip_user', 'select', 'ciip_guest', 'uuid=(select sub from ggircs_portal.session())'); + +end +$policy$; + +commit; diff --git a/schema/verify/policies/application_review_step_policies_001_drop_before_type_change.sql b/schema/verify/policies/application_review_step_policies_001_drop_before_type_change.sql new file mode 100644 index 0000000000..c66b661dfc --- /dev/null +++ b/schema/verify/policies/application_review_step_policies_001_drop_before_type_change.sql @@ -0,0 +1,7 @@ +-- Verify ggircs-portal:policies/application_review_step_policies_001_drop_before_type_change on pg + +begin; + +select ggircs_portal_private.verify_policy_not_present('ciip_industry_user_select_application_review_step', 'ggircs_portal.application_review_step'); + +rollback; diff --git a/schema/verify/policies/ciip_user_organisation_policies_001_drop_before_type_change.sql b/schema/verify/policies/ciip_user_organisation_policies_001_drop_before_type_change.sql new file mode 100644 index 0000000000..bb790fb3a0 --- /dev/null +++ b/schema/verify/policies/ciip_user_organisation_policies_001_drop_before_type_change.sql @@ -0,0 +1,8 @@ +-- Verify ggircs-portal:policies/ciip_user_organisation_policies_001_drop_before_type_change on pg + +begin; + +select ggircs_portal_private.verify_policy_not_present('ciip_industry_user_select_ciip_user_organisation', 'ggircs_portal.ciip_user_organisation'); +select ggircs_portal_private.verify_policy_not_present('ciip_industry_user_insert_ciip_user_organisation', 'ggircs_portal.ciip_user_organisation'); + +rollback; \ No newline at end of file diff --git a/schema/verify/policies/ciip_user_policies_001_drop_before_type_change.sql b/schema/verify/policies/ciip_user_policies_001_drop_before_type_change.sql new file mode 100644 index 0000000000..1d45c71315 --- /dev/null +++ b/schema/verify/policies/ciip_user_policies_001_drop_before_type_change.sql @@ -0,0 +1,11 @@ +-- Verify ggircs-portal:policies/ciip_user_policies_001_drop_before_type_change on pg + +begin; + +select ggircs_portal_private.verify_policy_not_present('ciip_analyst_insert_ciip_user', 'ggircs_portal.ciip_user'); +select ggircs_portal_private.verify_policy_not_present('ciip_analyst_update_ciip_user', 'ggircs_portal.ciip_user'); +select ggircs_portal_private.verify_policy_not_present('ciip_industry_user_insert_ciip_user', 'ggircs_portal.ciip_user'); +select ggircs_portal_private.verify_policy_not_present('ciip_industry_user_update_ciip_user', 'ggircs_portal.ciip_user'); +select ggircs_portal_private.verify_policy_not_present('ciip_guest_select_ciip_user', 'ggircs_portal.ciip_user'); + +rollback; \ No newline at end of file From f4dc142e39c82e3a99b25cd3cd249075af1a85b3 Mon Sep 17 00:00:00 2001 From: Pierre Bastianelli Date: Wed, 11 Jan 2023 17:29:46 -0800 Subject: [PATCH 06/35] refactor: ciip_user table with a uuid of type text --- schema/deploy/tables/ciip_user.sql | 89 +++++++--------------- schema/deploy/tables/ciip_user@v2.18.0.sql | 85 +++++++++++++++++++++ schema/deploy/types/jwt_token.sql | 2 - schema/revert/tables/ciip_user.sql | 11 ++- schema/revert/tables/ciip_user@v2.18.0.sql | 7 ++ schema/sqitch.plan | 6 +- schema/verify/tables/ciip_user@v2.18.0.sql | 28 +++++++ 7 files changed, 162 insertions(+), 66 deletions(-) create mode 100644 schema/deploy/tables/ciip_user@v2.18.0.sql create mode 100644 schema/revert/tables/ciip_user@v2.18.0.sql create mode 100644 schema/verify/tables/ciip_user@v2.18.0.sql diff --git a/schema/deploy/tables/ciip_user.sql b/schema/deploy/tables/ciip_user.sql index 16bd43da6b..865290792e 100644 --- a/schema/deploy/tables/ciip_user.sql +++ b/schema/deploy/tables/ciip_user.sql @@ -3,83 +3,50 @@ begin; -create table ggircs_portal.ciip_user -( - id integer primary key generated always as identity, - uuid uuid not null, - first_name varchar(1000), - last_name varchar(1000), - email_address varchar(1000), - occupation varchar(1000), - phone_number varchar(1000), - created_at timestamp with time zone not null default now(), - created_by int references ggircs_portal.ciip_user, - updated_at timestamp with time zone not null default now(), - updated_by int references ggircs_portal.ciip_user, - deleted_at timestamp with time zone, - deleted_by int references ggircs_portal.ciip_user +-- Disabling trigger before updating data in-place +alter table ggircs_portal.ciip_user disable trigger _100_timestamps; -); +-- Changing uuid to type varchar -create trigger _100_timestamps - before insert or update on ggircs_portal.ciip_user - for each row - execute procedure ggircs_portal_private.update_timestamps(); +-- ~~~NOTE FOR DEVELOPERS~~~ +-- We are not renaming this field to `session_sub` or something more appropriate, just due to the +-- the amount of refactoring work this would require. +-- As of this migration, it's implicit that the 'uuid' means 'the authentication provider's unique identifier' +alter table ggircs_portal.ciip_user alter column uuid type varchar(1000); -create unique index user_email_address_uindex - on ggircs_portal.ciip_user(email_address); +alter index ggircs_portal.user_email_address_uuuid rename to user_provider_uuid; - create unique index user_email_address_uuuid - on ggircs_portal.ciip_user(uuid); +-- Adding allow_uuid_update column +alter table ggircs_portal.ciip_user + add column allow_uuid_update boolean not null default false; -create trigger _welcome_email - after insert on ggircs_portal.ciip_user - for each row - execute procedure ggircs_portal_private.run_graphile_worker_job('welcome'); +create trigger user_uuid_immutable_with_flag + before update of uuid on ggircs_portal.ciip_user + for each row + execute function ggircs_portal_private.user_uuid_immutable_with_flag_set(); do $grant$ begin --- Grant ciip_administrator permissions -perform ggircs_portal_private.grant_permissions('select', 'ciip_user', 'ciip_administrator'); -perform ggircs_portal_private.grant_permissions('insert', 'ciip_user', 'ciip_administrator'); -perform ggircs_portal_private.grant_permissions('update', 'ciip_user', 'ciip_administrator', - ARRAY['first_name', 'last_name', 'email_address', 'occupation', 'phone_number', 'created_at', 'created_by', 'updated_at', 'updated_by', 'deleted_at', 'deleted_by']); --- Grant ciip_analyst permissions -perform ggircs_portal_private.grant_permissions('select', 'ciip_user', 'ciip_analyst'); -perform ggircs_portal_private.grant_permissions('insert', 'ciip_user', 'ciip_analyst'); +-- Permissions for allow_sub_update column +perform ggircs_portal_private.grant_permissions('update', 'ciip_user', 'ciip_administrator', + ARRAY['allow_uuid_update']); perform ggircs_portal_private.grant_permissions('update', 'ciip_user', 'ciip_analyst', - ARRAY['first_name', 'last_name', 'email_address', 'occupation', 'phone_number', 'created_at', 'created_by', 'updated_at', 'updated_by', 'deleted_at', 'deleted_by']); - --- Grant ciip_industry_user permissions -perform ggircs_portal_private.grant_permissions('select', 'ciip_user', 'ciip_industry_user'); -perform ggircs_portal_private.grant_permissions('insert', 'ciip_user', 'ciip_industry_user'); + ARRAY['allow_uuid_update']); perform ggircs_portal_private.grant_permissions('update', 'ciip_user', 'ciip_industry_user', - ARRAY['first_name', 'last_name', 'email_address', 'occupation', 'phone_number', 'created_at', 'created_by', 'updated_at', 'updated_by', 'deleted_at', 'deleted_by']); - --- Grant ciip_guest permissions -perform ggircs_portal_private.grant_permissions('select', 'ciip_user', 'ciip_guest'); + ARRAY['allow_uuid_update']); end $grant$; --- Enable row-level security -alter table ggircs_portal.ciip_user enable row level security; +update ggircs_portal.ciip_user set allow_uuid_update = true; + +-- Re-enabling trigger after data update +alter table ggircs_portal.ciip_user disable trigger _100_timestamps; + -comment on table ggircs_portal.ciip_user is 'Table containing the benchmark and eligibility threshold for a product'; -comment on column ggircs_portal.ciip_user.id is 'Unique ID for the user'; -comment on column ggircs_portal.ciip_user.uuid is 'Universally Unique ID for the user used for auth'; -comment on column ggircs_portal.ciip_user.first_name is 'User''s first name'; -comment on column ggircs_portal.ciip_user.last_name is 'User''s last name'; -comment on column ggircs_portal.ciip_user.email_address is 'User''s email address'; -comment on column ggircs_portal.ciip_user.occupation is 'User''s occupation'; -comment on column ggircs_portal.ciip_user.phone_number is 'User''s phone number'; -comment on column ggircs_portal.ciip_user.created_at is 'The date the user was updated'; -comment on column ggircs_portal.ciip_user.created_by is 'The person who updated the user'; -comment on column ggircs_portal.ciip_user.updated_at is 'The date the user was updated'; -comment on column ggircs_portal.ciip_user.updated_by is 'The person who updated the user'; -comment on column ggircs_portal.ciip_user.deleted_at is 'The date the user was deleted'; -comment on column ggircs_portal.ciip_user.deleted_by is 'The person who deleted the user'; +comment on column ggircs_portal.ciip_user.allow_uuid_update is 'Boolean value determines whether a legacy user can be updated. Legacy users may be updated only once.'; +comment on column ggircs_portal.ciip_user.uuid is 'Unique ID for the user/provider combination, defined by the single sign-on provider.'; commit; diff --git a/schema/deploy/tables/ciip_user@v2.18.0.sql b/schema/deploy/tables/ciip_user@v2.18.0.sql new file mode 100644 index 0000000000..16bd43da6b --- /dev/null +++ b/schema/deploy/tables/ciip_user@v2.18.0.sql @@ -0,0 +1,85 @@ +-- Deploy ggircs-portal:table_user to pg +-- requires: schema_ggircs_portal + +begin; + +create table ggircs_portal.ciip_user +( + id integer primary key generated always as identity, + uuid uuid not null, + first_name varchar(1000), + last_name varchar(1000), + email_address varchar(1000), + occupation varchar(1000), + phone_number varchar(1000), + created_at timestamp with time zone not null default now(), + created_by int references ggircs_portal.ciip_user, + updated_at timestamp with time zone not null default now(), + updated_by int references ggircs_portal.ciip_user, + deleted_at timestamp with time zone, + deleted_by int references ggircs_portal.ciip_user + +); + +create trigger _100_timestamps + before insert or update on ggircs_portal.ciip_user + for each row + execute procedure ggircs_portal_private.update_timestamps(); + +create unique index user_email_address_uindex + on ggircs_portal.ciip_user(email_address); + + create unique index user_email_address_uuuid + on ggircs_portal.ciip_user(uuid); + +create trigger _welcome_email + after insert on ggircs_portal.ciip_user + for each row + execute procedure ggircs_portal_private.run_graphile_worker_job('welcome'); + +do +$grant$ +begin +-- Grant ciip_administrator permissions +perform ggircs_portal_private.grant_permissions('select', 'ciip_user', 'ciip_administrator'); +perform ggircs_portal_private.grant_permissions('insert', 'ciip_user', 'ciip_administrator'); +perform ggircs_portal_private.grant_permissions('update', 'ciip_user', 'ciip_administrator', + ARRAY['first_name', 'last_name', 'email_address', 'occupation', 'phone_number', 'created_at', 'created_by', 'updated_at', 'updated_by', 'deleted_at', 'deleted_by']); + +-- Grant ciip_analyst permissions +perform ggircs_portal_private.grant_permissions('select', 'ciip_user', 'ciip_analyst'); +perform ggircs_portal_private.grant_permissions('insert', 'ciip_user', 'ciip_analyst'); +perform ggircs_portal_private.grant_permissions('update', 'ciip_user', 'ciip_analyst', + ARRAY['first_name', 'last_name', 'email_address', 'occupation', 'phone_number', 'created_at', 'created_by', 'updated_at', 'updated_by', 'deleted_at', 'deleted_by']); + +-- Grant ciip_industry_user permissions +perform ggircs_portal_private.grant_permissions('select', 'ciip_user', 'ciip_industry_user'); +perform ggircs_portal_private.grant_permissions('insert', 'ciip_user', 'ciip_industry_user'); +perform ggircs_portal_private.grant_permissions('update', 'ciip_user', 'ciip_industry_user', + ARRAY['first_name', 'last_name', 'email_address', 'occupation', 'phone_number', 'created_at', 'created_by', 'updated_at', 'updated_by', 'deleted_at', 'deleted_by']); + +-- Grant ciip_guest permissions +perform ggircs_portal_private.grant_permissions('select', 'ciip_user', 'ciip_guest'); + +end +$grant$; + +-- Enable row-level security +alter table ggircs_portal.ciip_user enable row level security; + +comment on table ggircs_portal.ciip_user is 'Table containing the benchmark and eligibility threshold for a product'; +comment on column ggircs_portal.ciip_user.id is 'Unique ID for the user'; +comment on column ggircs_portal.ciip_user.uuid is 'Universally Unique ID for the user used for auth'; +comment on column ggircs_portal.ciip_user.first_name is 'User''s first name'; +comment on column ggircs_portal.ciip_user.last_name is 'User''s last name'; +comment on column ggircs_portal.ciip_user.email_address is 'User''s email address'; +comment on column ggircs_portal.ciip_user.occupation is 'User''s occupation'; +comment on column ggircs_portal.ciip_user.phone_number is 'User''s phone number'; +comment on column ggircs_portal.ciip_user.created_at is 'The date the user was updated'; +comment on column ggircs_portal.ciip_user.created_by is 'The person who updated the user'; +comment on column ggircs_portal.ciip_user.updated_at is 'The date the user was updated'; +comment on column ggircs_portal.ciip_user.updated_by is 'The person who updated the user'; +comment on column ggircs_portal.ciip_user.deleted_at is 'The date the user was deleted'; +comment on column ggircs_portal.ciip_user.deleted_by is 'The person who deleted the user'; + +commit; diff --git a/schema/deploy/types/jwt_token.sql b/schema/deploy/types/jwt_token.sql index 686df09ae7..af4fe3259e 100644 --- a/schema/deploy/types/jwt_token.sql +++ b/schema/deploy/types/jwt_token.sql @@ -5,6 +5,4 @@ begin; alter type ggircs_portal.jwt_token alter attribute sub type text; -comment on type ggircs_portal.jwt_token is E'@primaryKey sub\n@foreignKey (sub) references ciip_user (session_sub)'; - commit; diff --git a/schema/revert/tables/ciip_user.sql b/schema/revert/tables/ciip_user.sql index c4d4793e56..d5ad82d5ba 100644 --- a/schema/revert/tables/ciip_user.sql +++ b/schema/revert/tables/ciip_user.sql @@ -1,7 +1,14 @@ --- Revert ggircs-portal:table_user from pg +-- Deploy ggircs-portal:table_user to pg +-- requires: schema_ggircs_portal begin; -drop table ggircs_portal.ciip_user; +drop trigger user_uuid_immutable_with_flag on ggircs_portal.ciip_user; + +alter table ggircs_portal.ciip_user alter column uuid type uuid using uuid::uuid; + +alter index ggircs_portal.user_provider_uuid rename to user_email_address_uuuid; + +comment on column ggircs_portal.ciip_user.uuid is 'Universally Unique ID for the user used for auth'; commit; diff --git a/schema/revert/tables/ciip_user@v2.18.0.sql b/schema/revert/tables/ciip_user@v2.18.0.sql new file mode 100644 index 0000000000..c4d4793e56 --- /dev/null +++ b/schema/revert/tables/ciip_user@v2.18.0.sql @@ -0,0 +1,7 @@ +-- Revert ggircs-portal:table_user from pg + +begin; + +drop table ggircs_portal.ciip_user; + +commit; diff --git a/schema/sqitch.plan b/schema/sqitch.plan index 9568ceee97..0e68ae6a5a 100644 --- a/schema/sqitch.plan +++ b/schema/sqitch.plan @@ -416,5 +416,9 @@ computed_columns/facility_application_has_swrs_report [queries/facility_applicat @v2.18.4 2022-11-04T20:58:09Z Dylan Leard # release v2.18.4 @v2.18.5 2022-11-21T17:08:07Z Dylan Leard # release v2.18.5 @v2.18.6 2022-11-22T23:04:34Z Dylan Leard # release v2.18.6 +policies/ciip_user_policies_001_drop_before_type_change 2023-01-12T00:53:56Z Pierre Bastianelli # dropping ciip_user RLS policy before we can change the type of the user uuid to varchar +policies/ciip_user_organisation_policies_001_drop_before_type_change 2023-01-12T00:40:10Z Pierre Bastianelli # dropping ciip_user_organisation RLS policy before we can change the type of the user uuid to varchar +policies/application_review_step_policies_001_drop_before_type_change 2023-01-12T01:13:09Z Pierre Bastianelli # dropping application_review_step RLS policy before we can change the type of the user uuid to varchar types/jwt_token [types/jwt_token@v2.18.0] 2023-01-11T00:01:27Z Pierre Bastianelli # Updating the jwt_token to have a sub of type text -trigger_functions/user_session_sub_immutable_with_flag 2023-01-11T00:07:27Z Pierre Bastianelli # Trigger that throws on update if the allow_sub_update flag is set to false +trigger_functions/user_uuid_immutable_with_flag 2023-01-11T00:07:27Z Pierre Bastianelli # Trigger that throws on update if the allow_sub_update flag is set to false +tables/ciip_user [tables/ciip_user@v2.18.0] 2023-01-11T19:48:19Z Pierre Bastianelli # Reworking the user table to contain a session_sub instead of a uuid field diff --git a/schema/verify/tables/ciip_user@v2.18.0.sql b/schema/verify/tables/ciip_user@v2.18.0.sql new file mode 100644 index 0000000000..bc2b1fd74d --- /dev/null +++ b/schema/verify/tables/ciip_user@v2.18.0.sql @@ -0,0 +1,28 @@ +-- Verify ggircs-portal:table_user on pg + +begin; + +select pg_catalog.has_table_privilege('ggircs_portal.ciip_user', 'select'); + +-- ciip_administrator Grants +select ggircs_portal_private.verify_grant('select', 'ciip_user', 'ciip_administrator'); +select ggircs_portal_private.verify_grant('insert', 'ciip_user', 'ciip_administrator'); +select ggircs_portal_private.verify_grant('update', 'ciip_user', 'ciip_administrator', + ARRAY['first_name', 'last_name', 'email_address', 'occupation', 'phone_number', 'created_at', 'created_by', 'updated_at', 'updated_by', 'deleted_at', 'deleted_by']); + +-- ciip_analyst Grants +select ggircs_portal_private.verify_grant('select', 'ciip_user', 'ciip_analyst'); +select ggircs_portal_private.verify_grant('insert', 'ciip_user', 'ciip_analyst'); +select ggircs_portal_private.verify_grant('update', 'ciip_user', 'ciip_analyst', + ARRAY['first_name', 'last_name', 'email_address', 'occupation', 'phone_number', 'created_at', 'created_by', 'updated_at', 'updated_by', 'deleted_at', 'deleted_by']); + +-- ciip_industry_user Grants +select ggircs_portal_private.verify_grant('select', 'ciip_user', 'ciip_industry_user'); +select ggircs_portal_private.verify_grant('insert', 'ciip_user', 'ciip_industry_user'); +select ggircs_portal_private.verify_grant('update', 'ciip_user', 'ciip_industry_user', + ARRAY['first_name', 'last_name', 'email_address', 'occupation', 'phone_number', 'created_at', 'created_by', 'updated_at', 'updated_by', 'deleted_at', 'deleted_by']); + +-- ciip_guest Grants +select ggircs_portal_private.verify_grant('select', 'ciip_user', 'ciip_guest'); + +rollback; From 19dbdac341d6428e75909e5cbaa08ad0f3686643 Mon Sep 17 00:00:00 2001 From: Pierre Bastianelli Date: Thu, 12 Jan 2023 14:08:50 -0800 Subject: [PATCH 07/35] refactor: updating set_user_id trigger with text type for uuid --- .../deploy/trigger_functions/set_user_id.sql | 2 +- .../trigger_functions/set_user_id@v2.18.0.sql | 35 +++++++++++++++++ .../revert/trigger_functions/set_user_id.sql | 6 +-- .../trigger_functions/set_user_id@v2.18.0.sql | 39 +++++++++++++++++++ schema/sqitch.plan | 1 + .../trigger_functions/set_user_id@v2.18.0.sql | 7 ++++ 6 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 schema/deploy/trigger_functions/set_user_id@v2.18.0.sql create mode 100644 schema/revert/trigger_functions/set_user_id@v2.18.0.sql create mode 100644 schema/verify/trigger_functions/set_user_id@v2.18.0.sql diff --git a/schema/deploy/trigger_functions/set_user_id.sql b/schema/deploy/trigger_functions/set_user_id.sql index ef1fe106b2..6a95916284 100644 --- a/schema/deploy/trigger_functions/set_user_id.sql +++ b/schema/deploy/trigger_functions/set_user_id.sql @@ -7,7 +7,7 @@ create or replace function ggircs_portal_private.set_user_id() returns trigger as $$ declare - user_sub uuid; + user_sub text; begin user_sub := (select sub from ggircs_portal.session()); new.user_id := (select id from ggircs_portal.ciip_user as cu where cu.uuid = user_sub); diff --git a/schema/deploy/trigger_functions/set_user_id@v2.18.0.sql b/schema/deploy/trigger_functions/set_user_id@v2.18.0.sql new file mode 100644 index 0000000000..ef1fe106b2 --- /dev/null +++ b/schema/deploy/trigger_functions/set_user_id@v2.18.0.sql @@ -0,0 +1,35 @@ +-- Deploy ggircs-portal:function_set_user_id to pg +-- requires: function_session +-- requires: table_ciip_user + +begin; +create or replace function ggircs_portal_private.set_user_id() + returns trigger as $$ + +declare + user_sub uuid; +begin + user_sub := (select sub from ggircs_portal.session()); + new.user_id := (select id from ggircs_portal.ciip_user as cu where cu.uuid = user_sub); + return new; +end; +$$ language plpgsql volatile; + +grant execute on function ggircs_portal_private.set_user_id to ciip_administrator, ciip_analyst, ciip_industry_user; + +comment on function ggircs_portal_private.set_user_id() + is $$ + a trigger to set a user_id foreign key column. + example usage: + + create table some_schema.some_table ( + user_id int references ggircs_portal.ciip_user(id) + ... + ); + create trigger _set_user_id + before update of some_column on some_schema.some_table + for each row + execute procedure ggircs_portal_private.set_user_id(); + $$; + +commit; diff --git a/schema/revert/trigger_functions/set_user_id.sql b/schema/revert/trigger_functions/set_user_id.sql index bf26c546f1..ef1fe106b2 100644 --- a/schema/revert/trigger_functions/set_user_id.sql +++ b/schema/revert/trigger_functions/set_user_id.sql @@ -10,11 +10,7 @@ declare user_sub uuid; begin user_sub := (select sub from ggircs_portal.session()); - if tg_argv[0] = 'certification_url' then - new.certified_by := (select id from ggircs_portal.ciip_user as cu where cu.uuid = user_sub); - else - new.user_id := (select id from ggircs_portal.ciip_user as cu where cu.uuid = user_sub); - end if; + new.user_id := (select id from ggircs_portal.ciip_user as cu where cu.uuid = user_sub); return new; end; $$ language plpgsql volatile; diff --git a/schema/revert/trigger_functions/set_user_id@v2.18.0.sql b/schema/revert/trigger_functions/set_user_id@v2.18.0.sql new file mode 100644 index 0000000000..bf26c546f1 --- /dev/null +++ b/schema/revert/trigger_functions/set_user_id@v2.18.0.sql @@ -0,0 +1,39 @@ +-- Deploy ggircs-portal:function_set_user_id to pg +-- requires: function_session +-- requires: table_ciip_user + +begin; +create or replace function ggircs_portal_private.set_user_id() + returns trigger as $$ + +declare + user_sub uuid; +begin + user_sub := (select sub from ggircs_portal.session()); + if tg_argv[0] = 'certification_url' then + new.certified_by := (select id from ggircs_portal.ciip_user as cu where cu.uuid = user_sub); + else + new.user_id := (select id from ggircs_portal.ciip_user as cu where cu.uuid = user_sub); + end if; + return new; +end; +$$ language plpgsql volatile; + +grant execute on function ggircs_portal_private.set_user_id to ciip_administrator, ciip_analyst, ciip_industry_user; + +comment on function ggircs_portal_private.set_user_id() + is $$ + a trigger to set a user_id foreign key column. + example usage: + + create table some_schema.some_table ( + user_id int references ggircs_portal.ciip_user(id) + ... + ); + create trigger _set_user_id + before update of some_column on some_schema.some_table + for each row + execute procedure ggircs_portal_private.set_user_id(); + $$; + +commit; diff --git a/schema/sqitch.plan b/schema/sqitch.plan index 0e68ae6a5a..abf9ab3032 100644 --- a/schema/sqitch.plan +++ b/schema/sqitch.plan @@ -422,3 +422,4 @@ policies/application_review_step_policies_001_drop_before_type_change 2023-01-12 types/jwt_token [types/jwt_token@v2.18.0] 2023-01-11T00:01:27Z Pierre Bastianelli # Updating the jwt_token to have a sub of type text trigger_functions/user_uuid_immutable_with_flag 2023-01-11T00:07:27Z Pierre Bastianelli # Trigger that throws on update if the allow_sub_update flag is set to false tables/ciip_user [tables/ciip_user@v2.18.0] 2023-01-11T19:48:19Z Pierre Bastianelli # Reworking the user table to contain a session_sub instead of a uuid field +trigger_functions/set_user_id [trigger_functions/set_user_id@v2.18.0] 2023-01-12T22:06:23Z Pierre Bastianelli # Updating the uuid type to text for a ciip user diff --git a/schema/verify/trigger_functions/set_user_id@v2.18.0.sql b/schema/verify/trigger_functions/set_user_id@v2.18.0.sql new file mode 100644 index 0000000000..f6ea31e376 --- /dev/null +++ b/schema/verify/trigger_functions/set_user_id@v2.18.0.sql @@ -0,0 +1,7 @@ +-- Verify ggircs-portal:function_set_user_id on pg + +begin; + +select pg_get_functiondef('ggircs_portal_private.set_user_id()'::regprocedure); + +rollback; From dc2fa04d2b1886d1a0025f25d0cd28032829a28c Mon Sep 17 00:00:00 2001 From: Pierre Bastianelli Date: Thu, 12 Jan 2023 14:24:23 -0800 Subject: [PATCH 08/35] refactor: update_timestamps doesnt depend on a uuid type --- .../trigger_functions/update_timestamps.sql | 2 +- .../update_timestamps@v2.18.0.sql | 59 +++++++++++++++++++ .../trigger_functions/update_timestamps.sql | 24 +++++--- .../update_timestamps@v2.18.0.sql | 53 +++++++++++++++++ schema/sqitch.plan | 1 + .../update_timestamps@v2.18.0.sql | 7 +++ 6 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 schema/deploy/trigger_functions/update_timestamps@v2.18.0.sql create mode 100644 schema/revert/trigger_functions/update_timestamps@v2.18.0.sql create mode 100644 schema/verify/trigger_functions/update_timestamps@v2.18.0.sql diff --git a/schema/deploy/trigger_functions/update_timestamps.sql b/schema/deploy/trigger_functions/update_timestamps.sql index 84c9336054..23280b3a29 100644 --- a/schema/deploy/trigger_functions/update_timestamps.sql +++ b/schema/deploy/trigger_functions/update_timestamps.sql @@ -7,7 +7,7 @@ create or replace function ggircs_portal_private.update_timestamps() returns trigger as $$ declare - user_sub uuid; + user_sub text; ciip_user_id int; begin diff --git a/schema/deploy/trigger_functions/update_timestamps@v2.18.0.sql b/schema/deploy/trigger_functions/update_timestamps@v2.18.0.sql new file mode 100644 index 0000000000..84c9336054 --- /dev/null +++ b/schema/deploy/trigger_functions/update_timestamps@v2.18.0.sql @@ -0,0 +1,59 @@ +-- Deploy ggircs-portal:function_update_timestamps to pg +-- requires: schema_ggircs_portal + +begin; + +create or replace function ggircs_portal_private.update_timestamps() + returns trigger as $$ + +declare + user_sub uuid; + ciip_user_id int; + +begin + user_sub := (select sub from ggircs_portal.session()); + ciip_user_id := (select id from ggircs_portal.ciip_user as cu where cu.uuid = user_sub); + if tg_op = 'INSERT' then + if to_jsonb(new) ? 'created_at' then + new.created_at = now(); + new.created_by = ciip_user_id; + end if; + if to_jsonb(new) ? 'updated_at' then + new.updated_at = now(); + new.updated_by = ciip_user_id; + end if; + elsif tg_op = 'UPDATE' then + if to_jsonb(new) ? 'deleted_at' then + if old.deleted_at is distinct from new.deleted_at and new.deleted_at is not null then + new.deleted_at = now(); + new.deleted_by = ciip_user_id; + end if; + end if; + if to_jsonb(new) ? 'updated_at' then + new.updated_at = greatest(now(), old.updated_at + interval '1 millisecond'); + new.updated_by = ciip_user_id; + end if; + end if; + return new; +end; +$$ language plpgsql; + +grant execute on function ggircs_portal_private.update_timestamps to ciip_administrator, ciip_analyst, ciip_industry_user; + +comment on function ggircs_portal_private.update_timestamps() + is $$ + a trigger to set created_at and updated_at columns. + example usage: + + create table some_schema.some_table ( + ... + created_at timestamp with time zone not null default now(), + updated_at timestamp with time zone not null default now() + ); + create trigger _100_timestamps + before insert or update on some_schema.some_table + for each row + execute procedure ggircs_portal_private.update_timestamps(); + $$; + +commit; diff --git a/schema/revert/trigger_functions/update_timestamps.sql b/schema/revert/trigger_functions/update_timestamps.sql index ca34228ca7..84c9336054 100644 --- a/schema/revert/trigger_functions/update_timestamps.sql +++ b/schema/revert/trigger_functions/update_timestamps.sql @@ -14,16 +14,22 @@ begin user_sub := (select sub from ggircs_portal.session()); ciip_user_id := (select id from ggircs_portal.ciip_user as cu where cu.uuid = user_sub); if tg_op = 'INSERT' then - new.created_at = now(); - new.created_by = ciip_user_id; - new.updated_at = now(); - new.updated_by = ciip_user_id; + if to_jsonb(new) ? 'created_at' then + new.created_at = now(); + new.created_by = ciip_user_id; + end if; + if to_jsonb(new) ? 'updated_at' then + new.updated_at = now(); + new.updated_by = ciip_user_id; + end if; elsif tg_op = 'UPDATE' then - if old.deleted_at is distinct from new.deleted_at then - new.deleted_at = now(); - new.deleted_by = ciip_user_id; - else - new.created_at = old.created_at; + if to_jsonb(new) ? 'deleted_at' then + if old.deleted_at is distinct from new.deleted_at and new.deleted_at is not null then + new.deleted_at = now(); + new.deleted_by = ciip_user_id; + end if; + end if; + if to_jsonb(new) ? 'updated_at' then new.updated_at = greatest(now(), old.updated_at + interval '1 millisecond'); new.updated_by = ciip_user_id; end if; diff --git a/schema/revert/trigger_functions/update_timestamps@v2.18.0.sql b/schema/revert/trigger_functions/update_timestamps@v2.18.0.sql new file mode 100644 index 0000000000..ca34228ca7 --- /dev/null +++ b/schema/revert/trigger_functions/update_timestamps@v2.18.0.sql @@ -0,0 +1,53 @@ +-- Deploy ggircs-portal:function_update_timestamps to pg +-- requires: schema_ggircs_portal + +begin; + +create or replace function ggircs_portal_private.update_timestamps() + returns trigger as $$ + +declare + user_sub uuid; + ciip_user_id int; + +begin + user_sub := (select sub from ggircs_portal.session()); + ciip_user_id := (select id from ggircs_portal.ciip_user as cu where cu.uuid = user_sub); + if tg_op = 'INSERT' then + new.created_at = now(); + new.created_by = ciip_user_id; + new.updated_at = now(); + new.updated_by = ciip_user_id; + elsif tg_op = 'UPDATE' then + if old.deleted_at is distinct from new.deleted_at then + new.deleted_at = now(); + new.deleted_by = ciip_user_id; + else + new.created_at = old.created_at; + new.updated_at = greatest(now(), old.updated_at + interval '1 millisecond'); + new.updated_by = ciip_user_id; + end if; + end if; + return new; +end; +$$ language plpgsql; + +grant execute on function ggircs_portal_private.update_timestamps to ciip_administrator, ciip_analyst, ciip_industry_user; + +comment on function ggircs_portal_private.update_timestamps() + is $$ + a trigger to set created_at and updated_at columns. + example usage: + + create table some_schema.some_table ( + ... + created_at timestamp with time zone not null default now(), + updated_at timestamp with time zone not null default now() + ); + create trigger _100_timestamps + before insert or update on some_schema.some_table + for each row + execute procedure ggircs_portal_private.update_timestamps(); + $$; + +commit; diff --git a/schema/sqitch.plan b/schema/sqitch.plan index abf9ab3032..194c5baace 100644 --- a/schema/sqitch.plan +++ b/schema/sqitch.plan @@ -423,3 +423,4 @@ types/jwt_token [types/jwt_token@v2.18.0] 2023-01-11T00:01:27Z Pierre Bastianell trigger_functions/user_uuid_immutable_with_flag 2023-01-11T00:07:27Z Pierre Bastianelli # Trigger that throws on update if the allow_sub_update flag is set to false tables/ciip_user [tables/ciip_user@v2.18.0] 2023-01-11T19:48:19Z Pierre Bastianelli # Reworking the user table to contain a session_sub instead of a uuid field trigger_functions/set_user_id [trigger_functions/set_user_id@v2.18.0] 2023-01-12T22:06:23Z Pierre Bastianelli # Updating the uuid type to text for a ciip user +trigger_functions/update_timestamps [trigger_functions/update_timestamps@v2.18.0] 2023-01-12T22:12:06Z Pierre Bastianelli # Updating the uuid type to text for ciip user diff --git a/schema/verify/trigger_functions/update_timestamps@v2.18.0.sql b/schema/verify/trigger_functions/update_timestamps@v2.18.0.sql new file mode 100644 index 0000000000..bdd5f83915 --- /dev/null +++ b/schema/verify/trigger_functions/update_timestamps@v2.18.0.sql @@ -0,0 +1,7 @@ +-- Verify ggircs-portal:function_update_timestamps on pg + +begin; + +select pg_get_functiondef('ggircs_portal_private.update_timestamps()'::regprocedure); + +rollback; From 58dd768c998364d73d8ae306b96f26fc5fd2025c Mon Sep 17 00:00:00 2001 From: Pierre Bastianelli Date: Thu, 12 Jan 2023 15:39:47 -0800 Subject: [PATCH 09/35] refactor: re-adding the policies removed before the change --- ...olicies_002_recreate_after_type_change.sql | 29 ++++++++++++++++++ ...olicies_002_recreate_after_type_change.sql | 30 +++++++++++++++++++ ...olicies_002_recreate_after_type_change.sql | 24 +++++++++++++++ ...olicies_002_recreate_after_type_change.sql | 7 +++++ ...olicies_002_recreate_after_type_change.sql | 8 +++++ ...olicies_002_recreate_after_type_change.sql | 12 ++++++++ schema/sqitch.plan | 3 ++ ...olicies_002_recreate_after_type_change.sql | 7 +++++ ...olicies_002_recreate_after_type_change.sql | 9 ++++++ ...olicies_002_recreate_after_type_change.sql | 16 ++++++++++ 10 files changed, 145 insertions(+) create mode 100644 schema/deploy/policies/application_review_step_policies_002_recreate_after_type_change.sql create mode 100644 schema/deploy/policies/ciip_user_organisation_policies_002_recreate_after_type_change.sql create mode 100644 schema/deploy/policies/ciip_user_policies_002_recreate_after_type_change.sql create mode 100644 schema/revert/policies/application_review_step_policies_002_recreate_after_type_change.sql create mode 100644 schema/revert/policies/ciip_user_organisation_policies_002_recreate_after_type_change.sql create mode 100644 schema/revert/policies/ciip_user_policies_002_recreate_after_type_change.sql create mode 100644 schema/verify/policies/application_review_step_policies_002_recreate_after_type_change.sql create mode 100644 schema/verify/policies/ciip_user_organisation_policies_002_recreate_after_type_change.sql create mode 100644 schema/verify/policies/ciip_user_policies_002_recreate_after_type_change.sql diff --git a/schema/deploy/policies/application_review_step_policies_002_recreate_after_type_change.sql b/schema/deploy/policies/application_review_step_policies_002_recreate_after_type_change.sql new file mode 100644 index 0000000000..375a9bd730 --- /dev/null +++ b/schema/deploy/policies/application_review_step_policies_002_recreate_after_type_change.sql @@ -0,0 +1,29 @@ +-- Deploy ggircs-portal:policies/application_review_step_policies_002_recreate_after_type_change to pg + +begin; + +do +$policy$ +declare + industry_user_statement text; +begin + +industry_user_statement = $$ + application_id in ( + select a.id from ggircs_portal.application a + join ggircs_portal.facility f + on a.facility_id = f.id + join ggircs_portal.ciip_user_organisation cuo + on f.organisation_id = cuo.organisation_id + join ggircs_portal.ciip_user cu + on cuo.user_id = cu.id + and cu.uuid = (select sub from ggircs_portal.session()) + ) +$$; + +perform ggircs_portal_private.upsert_policy('ciip_industry_user_select_application_review_step', 'application_review_step', 'select', 'ciip_industry_user', industry_user_statement); + +end +$policy$; + +commit; diff --git a/schema/deploy/policies/ciip_user_organisation_policies_002_recreate_after_type_change.sql b/schema/deploy/policies/ciip_user_organisation_policies_002_recreate_after_type_change.sql new file mode 100644 index 0000000000..d456055c35 --- /dev/null +++ b/schema/deploy/policies/ciip_user_organisation_policies_002_recreate_after_type_change.sql @@ -0,0 +1,30 @@ +-- Deploy ggircs-portal:policies/ciip_user_organisation_policies_002_recreate_after_type_change to pg + + +begin; + +do + $policy$ + begin + + -- ciip_industry_user RLS + perform ggircs_portal_private.upsert_policy( + 'ciip_industry_user_select_ciip_user_organisation', + 'ciip_user_organisation', + 'select', + 'ciip_industry_user', + $$user_id=(select id from ggircs_portal.ciip_user where uuid = (select sub from ggircs_portal.session()))$$ + ); + perform ggircs_portal_private.upsert_policy( + 'ciip_industry_user_insert_ciip_user_organisation', + 'ciip_user_organisation', + 'insert', + 'ciip_industry_user', + $$user_id=(select id from ggircs_portal.ciip_user where uuid = (select sub from ggircs_portal.session())) and status='pending'$$ + ); + + end + $policy$; + +commit; + diff --git a/schema/deploy/policies/ciip_user_policies_002_recreate_after_type_change.sql b/schema/deploy/policies/ciip_user_policies_002_recreate_after_type_change.sql new file mode 100644 index 0000000000..2987785cbb --- /dev/null +++ b/schema/deploy/policies/ciip_user_policies_002_recreate_after_type_change.sql @@ -0,0 +1,24 @@ +-- Deploy ggircs-portal:policies/ciip_user_policies_002_recreate_after_type_change to pg + +begin; + +do +$policy$ +begin + +-- ciip_analyst RLS +perform ggircs_portal_private.upsert_policy('ciip_analyst_insert_ciip_user', 'ciip_user', 'insert', 'ciip_analyst', 'uuid=(select sub from ggircs_portal.session())'); +perform ggircs_portal_private.upsert_policy('ciip_analyst_update_ciip_user', 'ciip_user', 'update', 'ciip_analyst', 'uuid=(select sub from ggircs_portal.session())'); + +-- ciip_industry_user RLS +perform ggircs_portal_private.upsert_policy('ciip_industry_user_insert_ciip_user', 'ciip_user', 'insert', 'ciip_industry_user', 'uuid=(select sub from ggircs_portal.session())'); +perform ggircs_portal_private.upsert_policy('ciip_industry_user_update_ciip_user', 'ciip_user', 'update', 'ciip_industry_user', 'uuid=(select sub from ggircs_portal.session())'); + +-- ciip_guest RLS +perform ggircs_portal_private.upsert_policy('ciip_guest_select_ciip_user', 'ciip_user', 'select', 'ciip_guest', 'uuid=(select sub from ggircs_portal.session())'); + +end +$policy$; + +commit; + diff --git a/schema/revert/policies/application_review_step_policies_002_recreate_after_type_change.sql b/schema/revert/policies/application_review_step_policies_002_recreate_after_type_change.sql new file mode 100644 index 0000000000..e852812fc5 --- /dev/null +++ b/schema/revert/policies/application_review_step_policies_002_recreate_after_type_change.sql @@ -0,0 +1,7 @@ +-- Revert ggircs-portal:policies/application_review_step_policies_002_recreate_after_type_change from pg + +begin; + +drop policy ciip_industry_user_select_application_review_step on ggircs_portal.application_review_step; + +commit; diff --git a/schema/revert/policies/ciip_user_organisation_policies_002_recreate_after_type_change.sql b/schema/revert/policies/ciip_user_organisation_policies_002_recreate_after_type_change.sql new file mode 100644 index 0000000000..d880512c22 --- /dev/null +++ b/schema/revert/policies/ciip_user_organisation_policies_002_recreate_after_type_change.sql @@ -0,0 +1,8 @@ +-- Revert ggircs-portal:policies/ciip_user_organisation_policies_002_recreate_after_type_change from pg + +begin; + +drop policy ciip_industry_user_select_ciip_user_organisation on ggircs_portal.ciip_user_organisation; +drop policy ciip_industry_user_insert_ciip_user_organisation on ggircs_portal.ciip_user_organisation; + +commit; diff --git a/schema/revert/policies/ciip_user_policies_002_recreate_after_type_change.sql b/schema/revert/policies/ciip_user_policies_002_recreate_after_type_change.sql new file mode 100644 index 0000000000..13fad1699f --- /dev/null +++ b/schema/revert/policies/ciip_user_policies_002_recreate_after_type_change.sql @@ -0,0 +1,12 @@ +-- Revert ggircs-portal:policies/ciip_user_policies_002_recreate_after_type_change from pg + +begin; + +drop policy ciip_analyst_insert_ciip_user on ggircs_portal.ciip_user; +drop policy ciip_analyst_update_ciip_user on ggircs_portal.ciip_user; +drop policy ciip_industry_user_insert_ciip_user on ggircs_portal.ciip_user; +drop policy ciip_industry_user_update_ciip_user on ggircs_portal.ciip_user; +drop policy ciip_guest_select_ciip_user on ggircs_portal.ciip_user; + +commit; + diff --git a/schema/sqitch.plan b/schema/sqitch.plan index 194c5baace..8a56326b1d 100644 --- a/schema/sqitch.plan +++ b/schema/sqitch.plan @@ -424,3 +424,6 @@ trigger_functions/user_uuid_immutable_with_flag 2023-01-11T00:07:27Z Pierre Bast tables/ciip_user [tables/ciip_user@v2.18.0] 2023-01-11T19:48:19Z Pierre Bastianelli # Reworking the user table to contain a session_sub instead of a uuid field trigger_functions/set_user_id [trigger_functions/set_user_id@v2.18.0] 2023-01-12T22:06:23Z Pierre Bastianelli # Updating the uuid type to text for a ciip user trigger_functions/update_timestamps [trigger_functions/update_timestamps@v2.18.0] 2023-01-12T22:12:06Z Pierre Bastianelli # Updating the uuid type to text for ciip user +policies/application_review_step_policies_002_recreate_after_type_change 2023-01-12T23:23:03Z Pierre Bastianelli # Recreating the policies impacted by the cif_user uuid type change +policies/ciip_user_organisation_policies_002_recreate_after_type_change 2023-01-12T23:33:28Z Pierre Bastianelli # Recreating the policies impacted by the cif_user uuid type change +policies/ciip_user_policies_002_recreate_after_type_change 2023-01-12T23:36:30Z Pierre Bastianelli # Recreating the policies impacted by the cif_user uuid type change diff --git a/schema/verify/policies/application_review_step_policies_002_recreate_after_type_change.sql b/schema/verify/policies/application_review_step_policies_002_recreate_after_type_change.sql new file mode 100644 index 0000000000..6cfaf28ac7 --- /dev/null +++ b/schema/verify/policies/application_review_step_policies_002_recreate_after_type_change.sql @@ -0,0 +1,7 @@ +-- Verify ggircs-portal:policies/application_review_step_policies_002_recreate_after_type_change on pg + +begin; + +select ggircs_portal_private.verify_policy('select', 'ciip_industry_user_select_application_review_step', 'application_review_step', 'ciip_industry_user'); + +rollback; diff --git a/schema/verify/policies/ciip_user_organisation_policies_002_recreate_after_type_change.sql b/schema/verify/policies/ciip_user_organisation_policies_002_recreate_after_type_change.sql new file mode 100644 index 0000000000..560347ed61 --- /dev/null +++ b/schema/verify/policies/ciip_user_organisation_policies_002_recreate_after_type_change.sql @@ -0,0 +1,9 @@ +-- Verify ggircs-portal:policies/ciip_user_organisation_policies_002_recreate_after_type_change on pg + +begin; + +-- ciip_industry_user Policies +select ggircs_portal_private.verify_policy('select', 'ciip_industry_user_select_ciip_user_organisation', 'ciip_user_organisation', 'ciip_industry_user'); +select ggircs_portal_private.verify_policy('insert', 'ciip_industry_user_insert_ciip_user_organisation', 'ciip_user_organisation', 'ciip_industry_user'); + +rollback; diff --git a/schema/verify/policies/ciip_user_policies_002_recreate_after_type_change.sql b/schema/verify/policies/ciip_user_policies_002_recreate_after_type_change.sql new file mode 100644 index 0000000000..117d04ca20 --- /dev/null +++ b/schema/verify/policies/ciip_user_policies_002_recreate_after_type_change.sql @@ -0,0 +1,16 @@ +-- Verify ggircs-portal:policies/ciip_user_policies_002_recreate_after_type_change on pg + +begin; + +-- ciip_analyst Policies +select ggircs_portal_private.verify_policy('insert', 'ciip_analyst_insert_ciip_user', 'ciip_user', 'ciip_analyst'); +select ggircs_portal_private.verify_policy('update', 'ciip_analyst_update_ciip_user', 'ciip_user', 'ciip_analyst'); + +-- ciip_industry_user Policies +select ggircs_portal_private.verify_policy('insert', 'ciip_industry_user_insert_ciip_user', 'ciip_user', 'ciip_industry_user'); +select ggircs_portal_private.verify_policy('update', 'ciip_industry_user_update_ciip_user', 'ciip_user', 'ciip_industry_user'); + +-- ciip_guest Policies +select ggircs_portal_private.verify_policy('select', 'ciip_guest_select_ciip_user', 'ciip_user', 'ciip_guest'); + +rollback; From 105a0e175f6b24069a7e4cb1ab86120c34464df7 Mon Sep 17 00:00:00 2001 From: Pierre Bastianelli Date: Fri, 13 Jan 2023 11:20:19 -0800 Subject: [PATCH 10/35] refactor: session middleware --- app/package.json | 2 +- app/server/db/databaseConnectionService.js | 6 +- app/server/index.js | 32 ++++---- app/server/middleware/session.js | 39 ++++++++++ app/server/schema.graphql | 49 +++++++++--- app/server/schema.json | 88 +++++++++++++++++++--- 6 files changed, 172 insertions(+), 44 deletions(-) create mode 100644 app/server/middleware/session.js diff --git a/app/package.json b/app/package.json index c2b210c502..662076a6ba 100644 --- a/app/package.json +++ b/app/package.json @@ -27,7 +27,7 @@ "lint": "eslint --ext .js,.jsx,.ts,.tsx ." }, "engines": { - "node": "14.17.5", + "node": "14.21.1", "yarn": "1.22.19" }, "author": "", diff --git a/app/server/db/databaseConnectionService.js b/app/server/db/databaseConnectionService.js index dfdb130277..38606eaf9b 100644 --- a/app/server/db/databaseConnectionService.js +++ b/app/server/db/databaseConnectionService.js @@ -38,8 +38,6 @@ const getDatabaseUrl = () => { return databaseURL; }; -const createConnectionPool = () => { - return new pg.Pool({ connectionString: getDatabaseUrl() }); -}; +const pgPool = new pg.Pool({ connectionString: getDatabaseUrl() }); -module.exports = { createConnectionPool, NO_AUTH_POSTGRES_ROLE }; +module.exports = { pgPool, getDatabaseUrl, NO_AUTH_POSTGRES_ROLE }; diff --git a/app/server/index.js b/app/server/index.js index 05e1005944..d629afbdc7 100644 --- a/app/server/index.js +++ b/app/server/index.js @@ -14,8 +14,6 @@ const port = Number.parseInt(process.env.PORT, 10) || 3004; const dev = process.env.NODE_ENV !== "production"; const app = nextjs({ dev }); const handle = app.getRequestHandler(); -const session = require("express-session"); -const PgSession = require("connect-pg-simple")(session); const bodyParser = require("body-parser"); const Keycloak = require("keycloak-connect"); const cors = require("cors"); @@ -27,10 +25,11 @@ const path = require("path"); const namespaceMap = require("../data/kc-namespace-map"); const redirectRouter = require("./redirects"); const cookieParser = require("cookie-parser"); -const databaseConnectionService = require("./db/databaseConnectionService"); +const { pgPool } = require("./db/databaseConnectionService"); const { createLightship } = require("lightship"); const delay = require("delay"); const { getSessionRemainingTime } = require("./helpers/keycloakHelpers"); +const session = require("./middleware/session"); /** * Override keycloak accessDenied handler to redirect to our 403 page @@ -59,8 +58,6 @@ if (!process.env.SESSION_SECRET) console.warn("SESSION_SECRET missing from environment"); const secret = process.env.SESSION_SECRET || crypto.randomBytes(32).toString(); -const pgPool = databaseConnectionService.createConnectionPool(); - // Graphile-worker function async function worker() { // Run a worker to execute jobs: @@ -142,20 +139,8 @@ app.prepare().then(async () => { else next(); }); - const store = new PgSession({ - pool: pgPool, - schemaName: "ggircs_portal_private", - tableName: "connect_session", - }); - server.use( - session({ - secret, - resave: false, - saveUninitialized: true, - cookie: { secure }, - store, - }) - ); + const { middleware: sessionMiddleware } = session(); + server.use(sessionMiddleware); // Keycloak instantiation for dev/test/prod const kcNamespace = process.env.NAMESPACE @@ -176,6 +161,15 @@ app.prepare().then(async () => { }&response_type=code&scope=openid&redirect_uri=${encodeURIComponent( `${process.env.HOST}/login?auth_callback=1` )}`; + + const expressSession = require("express-session"); + const connectPgSimple = require("connect-pg-simple"); + const PgSession = connectPgSimple(expressSession); + const store = new PgSession({ + pool: pgPool, + schemaName: "ggircs_portal_private", + tableName: "connect_session", + }); const keycloak = new Keycloak({ store }, kcConfig); // Nuke the siteminder session token on logout if we can diff --git a/app/server/middleware/session.js b/app/server/middleware/session.js new file mode 100644 index 0000000000..5a5c0ffea8 --- /dev/null +++ b/app/server/middleware/session.js @@ -0,0 +1,39 @@ +const expressSession = require("express-session"); +const connectPgSimple = require("connect-pg-simple"); +const crypto = require("crypto"); +const dotenv = require("dotenv"); +const { pgPool } = require("../db/databaseConnectionService"); + +dotenv.config(); + +const PgSession = connectPgSimple(expressSession); +// True if the host has been configured to use https +const secure = /^https/.test(process.env.HOST); +// Ensure we properly crypt our cookie session store with a pre-shared key in a secure environment +if (secure && typeof process.env.SESSION_SECRET !== typeof String()) + throw new Error("export SESSION_SECRET to encrypt session cookies"); +if (secure && process.env.SESSION_SECRET.length < 24) + throw new Error("exported SESSION_SECRET must be at least 24 characters"); +if (!process.env.SESSION_SECRET) + console.warn("SESSION_SECRET missing from environment"); +const secret = process.env.SESSION_SECRET || crypto.randomBytes(32).toString(); + +const session = () => { + const store = new PgSession({ + pool: pgPool, + schemaName: "ggircs_portal_private", + tableName: "connect_session", + }); + + const middleware = expressSession({ + secret, + resave: false, + saveUninitialized: true, + cookie: { secure }, + store, + }); + + return { middleware, store }; +}; + +module.exports = session; diff --git a/app/server/schema.graphql b/app/server/schema.graphql index 1618d8773d..4c61220899 100644 --- a/app/server/schema.graphql +++ b/app/server/schema.graphql @@ -5404,6 +5404,11 @@ input CiipProductStateFilter { """Table containing the benchmark and eligibility threshold for a product""" type CiipUser implements Node { + """ + Boolean value determines whether a legacy user can be updated. Legacy users may be updated only once. + """ + allowUuidUpdate: Boolean! + """Reads and enables pagination through a set of `ApplicationReviewStep`.""" applicationReviewStepsByReviewCommentCreatedByAndApplicationReviewStepId( """Read all values in the set after (below) this cursor.""" @@ -12538,8 +12543,10 @@ type CiipUser implements Node { """The person who updated the user""" updatedBy: Int - """Universally Unique ID for the user used for auth""" - uuid: UUID! + """ + Unique ID for the user/provider combination, defined by the single sign-on provider. + """ + uuid: String! } """ @@ -20223,6 +20230,9 @@ A condition to be used against `CiipUser` object types. All fields are tested for equality and combined with a logical ‘and.’ """ input CiipUserCondition { + """Checks for equality with the object’s `allowUuidUpdate` field.""" + allowUuidUpdate: Boolean + """Checks for equality with the object’s `createdAt` field.""" createdAt: Datetime @@ -20260,7 +20270,7 @@ input CiipUserCondition { updatedBy: Int """Checks for equality with the object’s `uuid` field.""" - uuid: UUID + uuid: String } """ @@ -20465,6 +20475,9 @@ type CiipUserEmissionCategoriesByEmissionCategoryGasUpdatedByAndEmissionCategory A filter to be used against `CiipUser` object types. All fields are combined with a logical ‘and.’ """ input CiipUserFilter { + """Filter by the object’s `allowUuidUpdate` field.""" + allowUuidUpdate: BooleanFilter + """Checks for all expressions in this list.""" and: [CiipUserFilter!] @@ -20871,7 +20884,7 @@ input CiipUserFilter { updatedBy: IntFilter """Filter by the object’s `uuid` field.""" - uuid: UUIDFilter + uuid: StringFilter } """ @@ -21248,6 +21261,11 @@ type CiipUserGasesByEmissionCategoryGasUpdatedByAndGasIdManyToManyEdge { """An input for mutations affecting `CiipUser`""" input CiipUserInput { + """ + Boolean value determines whether a legacy user can be updated. Legacy users may be updated only once. + """ + allowUuidUpdate: Boolean + """The date the user was updated""" createdAt: Datetime @@ -21281,8 +21299,10 @@ input CiipUserInput { """The person who updated the user""" updatedBy: Int - """Universally Unique ID for the user used for auth""" - uuid: UUID! + """ + Unique ID for the user/provider combination, defined by the single sign-on provider. + """ + uuid: String! } """ @@ -21784,6 +21804,11 @@ enum CiipUserOrganisationsOrderBy { Represents an update to a `CiipUser`. Fields that are set will be updated. """ input CiipUserPatch { + """ + Boolean value determines whether a legacy user can be updated. Legacy users may be updated only once. + """ + allowUuidUpdate: Boolean + """The date the user was updated""" createdAt: Datetime @@ -21817,8 +21842,10 @@ input CiipUserPatch { """The person who updated the user""" updatedBy: Int - """Universally Unique ID for the user used for auth""" - uuid: UUID + """ + Unique ID for the user/provider combination, defined by the single sign-on provider. + """ + uuid: String } """ @@ -23429,6 +23456,8 @@ type CiipUsersEdge { """Methods to use when ordering `CiipUser`.""" enum CiipUsersOrderBy { + ALLOW_UUID_UPDATE_ASC + ALLOW_UUID_UPDATE_DESC CREATED_AT_ASC CREATED_AT_DESC CREATED_BY_ASC @@ -31375,7 +31404,7 @@ type JwtToken { preferredUsername: String priorityGroup: String sessionState: UUID - sub: UUID + sub: String typ: String userGroups: [String] } @@ -31454,7 +31483,7 @@ input JwtTokenFilter { sessionState: UUIDFilter """Filter by the object’s `sub` field.""" - sub: UUIDFilter + sub: StringFilter """Filter by the object’s `typ` field.""" typ: StringFilter diff --git a/app/server/schema.json b/app/server/schema.json index 9318238519..258690b142 100644 --- a/app/server/schema.json +++ b/app/server/schema.json @@ -18764,6 +18764,22 @@ "name": "CiipUser", "description": "Table containing the benchmark and eligibility threshold for a product", "fields": [ + { + "name": "allowUuidUpdate", + "description": "Boolean value determines whether a legacy user can be updated. Legacy users may be updated only once.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "applicationReviewStepsByReviewCommentCreatedByAndApplicationReviewStepId", "description": "Reads and enables pagination through a set of `ApplicationReviewStep`.", @@ -40814,14 +40830,14 @@ }, { "name": "uuid", - "description": "Universally Unique ID for the user used for auth", + "description": "Unique ID for the user/provider combination, defined by the single sign-on provider.", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "UUID", + "name": "String", "ofType": null } }, @@ -68540,6 +68556,16 @@ "description": "A condition to be used against `CiipUser` object types. All fields are tested\nfor equality and combined with a logical ‘and.’", "fields": null, "inputFields": [ + { + "name": "allowUuidUpdate", + "description": "Checks for equality with the object’s `allowUuidUpdate` field.", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null + }, { "name": "createdAt", "description": "Checks for equality with the object’s `createdAt` field.", @@ -68665,7 +68691,7 @@ "description": "Checks for equality with the object’s `uuid` field.", "type": { "kind": "SCALAR", - "name": "UUID", + "name": "String", "ofType": null }, "defaultValue": null @@ -69362,6 +69388,16 @@ "description": "A filter to be used against `CiipUser` object types. All fields are combined with a logical ‘and.’", "fields": null, "inputFields": [ + { + "name": "allowUuidUpdate", + "description": "Filter by the object’s `allowUuidUpdate` field.", + "type": { + "kind": "INPUT_OBJECT", + "name": "BooleanFilter", + "ofType": null + }, + "defaultValue": null + }, { "name": "and", "description": "Checks for all expressions in this list.", @@ -70693,7 +70729,7 @@ "description": "Filter by the object’s `uuid` field.", "type": { "kind": "INPUT_OBJECT", - "name": "UUIDFilter", + "name": "StringFilter", "ofType": null }, "defaultValue": null @@ -72071,6 +72107,16 @@ "description": "An input for mutations affecting `CiipUser`", "fields": null, "inputFields": [ + { + "name": "allowUuidUpdate", + "description": "Boolean value determines whether a legacy user can be updated. Legacy users may be updated only once.", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null + }, { "name": "createdAt", "description": "The date the user was updated", @@ -72183,13 +72229,13 @@ }, { "name": "uuid", - "description": "Universally Unique ID for the user used for auth", + "description": "Unique ID for the user/provider combination, defined by the single sign-on provider.", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "UUID", + "name": "String", "ofType": null } }, @@ -73990,6 +74036,16 @@ "description": "Represents an update to a `CiipUser`. Fields that are set will be updated.", "fields": null, "inputFields": [ + { + "name": "allowUuidUpdate", + "description": "Boolean value determines whether a legacy user can be updated. Legacy users may be updated only once.", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null + }, { "name": "createdAt", "description": "The date the user was updated", @@ -74102,10 +74158,10 @@ }, { "name": "uuid", - "description": "Universally Unique ID for the user used for auth", + "description": "Unique ID for the user/provider combination, defined by the single sign-on provider.", "type": { "kind": "SCALAR", - "name": "UUID", + "name": "String", "ofType": null }, "defaultValue": null @@ -79378,6 +79434,18 @@ "inputFields": null, "interfaces": null, "enumValues": [ + { + "name": "ALLOW_UUID_UPDATE_ASC", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ALLOW_UUID_UPDATE_DESC", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "CREATED_AT_ASC", "description": null, @@ -107668,7 +107736,7 @@ "args": [], "type": { "kind": "SCALAR", - "name": "UUID", + "name": "String", "ofType": null }, "isDeprecated": false, @@ -107965,7 +108033,7 @@ "description": "Filter by the object’s `sub` field.", "type": { "kind": "INPUT_OBJECT", - "name": "UUIDFilter", + "name": "StringFilter", "ofType": null }, "defaultValue": null From c92885900caa41708fd12983f695074adc3c42fe Mon Sep 17 00:00:00 2001 From: Pierre Bastianelli Date: Fri, 13 Jan 2023 13:17:13 -0800 Subject: [PATCH 11/35] chore: updating nodejs --- .tool-versions | 2 +- app/package.json | 2 ++ app/yarn.lock | 30 ++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 93926c867b..cb1bb9c23e 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,4 +1,4 @@ -nodejs 14.17.5 +nodejs 14.21.1 yarn 1.22.19 postgres 14.0 python 3.9.2 diff --git a/app/package.json b/app/package.json index 662076a6ba..174eb688c5 100644 --- a/app/package.json +++ b/app/package.json @@ -33,6 +33,7 @@ "author": "", "license": "ISC", "dependencies": { + "@bcgov-cas/sso-express": "^3.2.0", "@fortawesome/fontawesome-svg-core": "^1.2.31", "@fortawesome/free-solid-svg-icons": "^5.15.3", "@fortawesome/react-fontawesome": "^0.1.16", @@ -68,6 +69,7 @@ "morgan": "^1.10.0", "next": "^12.1.0", "nodemailer": "^6.6.1", + "openid-client": "5", "pg": "^8.2.0", "postgraphile": "^4.11.0", "postgraphile-log-consola": "^1.0.1", diff --git a/app/yarn.lock b/app/yarn.lock index 931690b563..4d752279f6 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -844,6 +844,11 @@ "@babel/helper-validator-identifier" "^7.14.0" to-fast-properties "^2.0.0" +"@bcgov-cas/sso-express@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@bcgov-cas/sso-express/-/sso-express-3.2.0.tgz#1f857d9590539b23d082e1ec1300ac9fac4865e3" + integrity sha512-DXL201txrrTG9mf8Qpn9/S8GlbqMHR2Mf55UWiNE3jCxLX+/gxGIX8Vt1i2xppVMrh+zSVlRDP8qZndK2iVDLw== + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -7672,6 +7677,11 @@ joi@^17.6.0: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" +jose@^4.10.0: + version "4.11.2" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.11.2.tgz#d9699307c02e18ff56825843ba90e2fae9f09e23" + integrity sha512-njj0VL2TsIxCtgzhO+9RRobBvws4oYyCM8TpvoUQwl/MbIM3NFJRR9+e6x0sS5xXaP1t6OCBkaBME98OV9zU5A== + js-file-download@^0.4.12: version "0.4.12" resolved "https://registry.yarnpkg.com/js-file-download/-/js-file-download-0.4.12.tgz#10c70ef362559a5b23cdbdc3bd6f399c3d91d821" @@ -8850,6 +8860,11 @@ object-hash@^1.3.1: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== +object-hash@^2.0.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + object-inspect@^1.10.3: version "1.10.3" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" @@ -8984,6 +8999,11 @@ on-finished@2.4.1: integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== dependencies: ee-first "1.1.1" + +oidc-token-hash@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz#ae6beec3ec20f0fd885e5400d175191d6e2f10c6" + integrity sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ== on-finished@~2.3.0: version "2.3.0" @@ -9037,6 +9057,16 @@ ono@^4.0.11: dependencies: format-util "^1.0.3" +openid-client@5: + version "5.3.1" + resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.3.1.tgz#69a5fa7d2b5ad479032f576852d40b4d4435488a" + integrity sha512-RLfehQiHch9N6tRWNx68cicf3b1WR0x74bJWHRc25uYIbSRwjxYcTFaRnzbbpls5jroLAaB/bFIodTgA5LJMvw== + dependencies: + jose "^4.10.0" + lru-cache "^6.0.0" + object-hash "^2.0.1" + oidc-token-hash "^5.0.1" + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" From b8125df41cf1294d6900dfa9c030bd5b6d6e2a6f Mon Sep 17 00:00:00 2001 From: Pierre Bastianelli Date: Fri, 13 Jan 2023 13:17:40 -0800 Subject: [PATCH 12/35] refactor: using sso-express to login with the new kc gold instance --- app/.env.example | 2 + app/data/kc-namespace-map.json | 5 - app/server/helpers/keycloakHelpers.js | 8 - app/server/helpers/userGroupAuthentication.js | 26 ++-- app/server/index.js | 138 +----------------- app/server/middleware/sso.js | 71 +++++++++ .../postgraphile/authenticationPgSettings.js | 22 ++- 7 files changed, 96 insertions(+), 176 deletions(-) delete mode 100644 app/data/kc-namespace-map.json delete mode 100644 app/server/helpers/keycloakHelpers.js create mode 100644 app/server/middleware/sso.js diff --git a/app/.env.example b/app/.env.example index 71709c4d7c..cdf3a394ed 100644 --- a/app/.env.example +++ b/app/.env.example @@ -9,6 +9,8 @@ HOST= GGIRCS_HOST= METABASE_HOST= +KC_CLIENT_SECRET= + # true if we want the mocks database schema to be deployed, false otherwise ENABLE_DB_MOCKS= ENABLE_DB_MOCKS_COOKIES_ONLY= diff --git a/app/data/kc-namespace-map.json b/app/data/kc-namespace-map.json deleted file mode 100644 index 8bda0d4ebb..0000000000 --- a/app/data/kc-namespace-map.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "09269b-dev": "dev.", - "09269b-test": "test.", - "09269b-prod": "" -} diff --git a/app/server/helpers/keycloakHelpers.js b/app/server/helpers/keycloakHelpers.js deleted file mode 100644 index 6b9ff5f363..0000000000 --- a/app/server/helpers/keycloakHelpers.js +++ /dev/null @@ -1,8 +0,0 @@ -async function getSessionRemainingTime(keycloak, req, res) { - const grant = await keycloak.getGrant(req, res); - return Math.round(grant.refresh_token.content.exp - Date.now() / 1000); -} - -module.exports = { - getSessionRemainingTime, -}; diff --git a/app/server/helpers/userGroupAuthentication.js b/app/server/helpers/userGroupAuthentication.js index f22527d2c2..b87ae46024 100644 --- a/app/server/helpers/userGroupAuthentication.js +++ b/app/server/helpers/userGroupAuthentication.js @@ -1,22 +1,20 @@ +const { isAuthenticated } = require("@bcgov-cas/sso-express/dist/helpers"); + const groupConstants = require("../../data/group-constants"); const { compactGroups } = require("../../lib/user-groups"); -const removeFirstLetter = (str) => str.slice(1); +const removeLeadingSlash = (str) => (str[0] === "/" ? str.slice(1) : str); const getUserGroups = (req) => { - if ( - !req.kauth || - !req.kauth.grant || - !req.kauth.grant.id_token || - !req.kauth.grant.id_token.content || - !req.kauth.grant.id_token.content.groups - ) - return [groupConstants.GUEST]; - - const identityProvider = req.kauth.grant.id_token.content.identity_provider; - const { groups } = req.kauth.grant.id_token.content; - - const processedGroups = groups.map((value) => removeFirstLetter(value)); + if (process.argv.includes("AS_CYPRESS") && req.cookies["mocks.auth"]) { + return [req.cookies["mocks.auth"]]; + } + if (!isAuthenticated(req)) return [groupConstants.GUEST]; + + const identityProvider = req.claims.identity_provider; + const groups = req.claims.groups || []; + + const processedGroups = groups.map((value) => removeLeadingSlash(value)); const validGroups = compactGroups(processedGroups); if (validGroups.length === 0) { diff --git a/app/server/index.js b/app/server/index.js index d629afbdc7..cc645dea48 100644 --- a/app/server/index.js +++ b/app/server/index.js @@ -17,47 +17,21 @@ const handle = app.getRequestHandler(); const bodyParser = require("body-parser"); const Keycloak = require("keycloak-connect"); const cors = require("cors"); -const { getUserGroupLandingRoute } = require("../lib/user-groups"); -const { getUserGroups } = require("./helpers/userGroupAuthentication"); const UNSUPPORTED_BROWSERS = require("../data/unsupported-browsers"); const { run } = require("graphile-worker"); const path = require("path"); -const namespaceMap = require("../data/kc-namespace-map"); const redirectRouter = require("./redirects"); const cookieParser = require("cookie-parser"); const { pgPool } = require("./db/databaseConnectionService"); const { createLightship } = require("lightship"); const delay = require("delay"); -const { getSessionRemainingTime } = require("./helpers/keycloakHelpers"); const session = require("./middleware/session"); +const ssoMiddleware = require("./middleware/sso"); -/** - * Override keycloak accessDenied handler to redirect to our 403 page - */ -Keycloak.prototype.accessDenied = ({ res }) => res.redirect("/403"); - -const NO_AUTH = process.argv.includes("NO_AUTH"); -const AS_REPORTER = process.argv.includes("AS_REPORTER"); -const AS_ANALYST = process.argv.includes("AS_ANALYST"); -const AS_ADMIN = process.argv.includes("AS_ADMIN"); -const AS_PENDING = process.argv.includes("AS_PENDING"); const NO_MAIL = process.argv.includes("NO_MAIL"); -const AS_CYPRESS = process.argv.includes("AS_CYPRESS"); if (NO_MAIL) process.env.NO_MAIL = true; -// True if the host has been configured to use https -const secure = /^https/.test(process.env.HOST); - -// Ensure we properly crypt our cookie session store with a pre-shared key in a secure environment -if (secure && typeof process.env.SESSION_SECRET !== typeof String()) - throw new Error("export SESSION_SECRET to encrypt session cookies"); -if (secure && process.env.SESSION_SECRET.length < 24) - throw new Error("exported SESSION_SECRET must be at least 24 characters"); -if (!process.env.SESSION_SECRET) - console.warn("SESSION_SECRET missing from environment"); -const secret = process.env.SESSION_SECRET || crypto.randomBytes(32).toString(); - // Graphile-worker function async function worker() { // Run a worker to execute jobs: @@ -87,14 +61,6 @@ worker().catch((error) => { } }); -const getRedirectURL = (req) => { - if (req.query.redirectTo) return req.query.redirectTo; - - const groups = getUserGroups(req); - - return getUserGroupLandingRoute(groups); -}; - app.prepare().then(async () => { const server = express(); @@ -124,7 +90,6 @@ app.prepare().then(async () => { if (!process.env.NAMESPACE || !process.env.NAMESPACE.endsWith("-prod")) { res.append("X-Robots-Tag", "noindex, noimageindex, nofollow, noarchive"); } - next(); }); @@ -141,93 +106,7 @@ app.prepare().then(async () => { const { middleware: sessionMiddleware } = session(); server.use(sessionMiddleware); - - // Keycloak instantiation for dev/test/prod - const kcNamespace = process.env.NAMESPACE - ? namespaceMap[process.env.NAMESPACE] - : "dev."; - const kcConfig = { - realm: "pisrwwhx", - "auth-server-url": `https://${kcNamespace}oidc.gov.bc.ca/auth`, - "ssl-required": "external", - resource: "cas-ciip-portal", - "public-client": true, - "confidential-port": 0, - }; - const kcRegistrationUrl = `${kcConfig["auth-server-url"]}/realms/${ - kcConfig.realm - }/protocol/openid-connect/registrations?client_id=${ - kcConfig.resource - }&response_type=code&scope=openid&redirect_uri=${encodeURIComponent( - `${process.env.HOST}/login?auth_callback=1` - )}`; - - const expressSession = require("express-session"); - const connectPgSimple = require("connect-pg-simple"); - const PgSession = connectPgSimple(expressSession); - const store = new PgSession({ - pool: pgPool, - schemaName: "ggircs_portal_private", - tableName: "connect_session", - }); - const keycloak = new Keycloak({ store }, kcConfig); - - // Nuke the siteminder session token on logout if we can - // this will be ignored by the user agent unless we're - // currently deployed to a subdomain of gov.bc.ca - server.post("/logout", (_req, res, next) => { - res.clearCookie("SMSESSION", { domain: ".gov.bc.ca", secure: true }); - next(); - }); - - // Retrieves keycloak grant for the session - server.use( - keycloak.middleware({ - logout: "/logout", - admin: "/", - }) - ); - - // Returns the time, in seconds, before the refresh_token expires. - // This corresponds to the SSO idle timeout configured in keycloak. - server.get("/session-idle-remaining-time", async (req, res) => { - if ( - NO_AUTH || - AS_ADMIN || - AS_ANALYST || - AS_PENDING || - AS_REPORTER || - AS_CYPRESS - ) { - return res.json(3600); - } - - if (!req.kauth || !req.kauth.grant) { - return res.json(null); - } - - return res.json(await getSessionRemainingTime(keycloak, req, res)); - }); - - // For any request (other than getting the remaining idle time), refresh the grant - // if needed. If the access token is expired (defaults to 5min in keycloak), - // the refresh token will be used to get a new access token, and the refresh token expiry will be updated. - server.use(async (req, res, next) => { - if (req.kauth && req.kauth.grant) { - try { - const grant = await keycloak.getGrant(req, res); - await keycloak.grantManager.ensureFreshness(grant); - } catch (error) { - return next(error); - } - } - next(); - }); - - // This ensures grant freshness with the next directive - we just return a success response code. - server.get("/extend-session", async (req, res) => { - return res.json(await getSessionRemainingTime(keycloak, req, res)); - }); + server.use(await ssoMiddleware()); server.use(cookieParser()); @@ -250,19 +129,6 @@ app.prepare().then(async () => { }) ); - if (NO_AUTH || AS_ANALYST || AS_REPORTER || AS_ADMIN) - server.post("/login", (req, res) => res.redirect(302, getRedirectURL(req))); - else - server.post("/login", keycloak.protect(), (req, res) => - // This request handler gets called on a POST to /login if the user is already authenticated - res.redirect(302, getRedirectURL(req)) - ); - - // Keycloak callbak; do not keycloak.protect() to avoid users being authenticated against their will via XSS attack - server.get("/login", (req, res) => res.redirect(302, getRedirectURL(req))); - - server.get("/register", ({ res }) => res.redirect(302, kcRegistrationUrl)); - server.get("*", async (req, res) => { return handle(req, res); }); diff --git a/app/server/middleware/sso.js b/app/server/middleware/sso.js new file mode 100644 index 0000000000..d67cf604a7 --- /dev/null +++ b/app/server/middleware/sso.js @@ -0,0 +1,71 @@ +const { default: ssoExpress } = require("@bcgov-cas/sso-express"); +const dotenv = require("dotenv"); +const { getUserGroupLandingRoute } = require("../../lib/user-groups"); +const { getUserGroups } = require("../helpers/userGroupAuthentication"); + +dotenv.config(); + +const NO_AUTH = process.argv.includes("NO_AUTH"); +const AS_REPORTER = process.argv.includes("AS_REPORTER"); +const AS_ANALYST = process.argv.includes("AS_ANALYST"); +const AS_ADMIN = process.argv.includes("AS_ADMIN"); +const AS_PENDING = process.argv.includes("AS_PENDING"); +const AS_CYPRESS = process.argv.includes("AS_CYPRESS"); + +const mockLogin = + NO_AUTH || AS_ADMIN || AS_ANALYST || AS_PENDING || AS_REPORTER || AS_CYPRESS; + +const host = process.env.HOST || "http://localhost:3004"; +const namespace = process.env.NAMESPACE; +const kcClientSecret = process.env.KC_CLIENT_SECRET; + +let ssoServerHost = ""; +if (!namespace || namespace.endsWith("-dev")) + ssoServerHost = "dev.loginproxy.gov.bc.ca"; +else if (namespace.endsWith("-test")) + ssoServerHost = "test.loginproxy.gov.bc.ca"; +else ssoServerHost = "loginproxy.gov.bc.ca"; + +async function middleware() { + return ssoExpress({ + applicationDomain: ".gov.bc.ca", + getLandingRoute: (req) => { + console.log("============================= LANDING ROUTE"); + console.log(""); + if (req.query.redirectTo) return req.query.redirectTo; + + const groups = getUserGroups(req); + console.log("GROUPS ", groups); + + return getUserGroupLandingRoute(groups); + }, + getRedirectUri: (defaultRedirectUri, req) => { + const redirectUri = new URL(defaultRedirectUri); + if (req.query.redirectTo) + redirectUri.searchParams.append( + "redirectTo", + req.query.redirectTo.toString() + ); + + return redirectUri; + }, + bypassAuthentication: { + login: mockLogin, + sessionIdleRemainingTime: mockLogin, + }, + oidcConfig: { + baseUrl: host, + clientId: "ciip-4440", + oidcIssuer: `https://${ssoServerHost}/auth/realms/standard`, + clientSecret: kcClientSecret, + }, + authorizationUrlParams: (req) => { + if (req.query.kc_idp_hint === "idir") return { kc_idp_hint: "idir" }; + if (req.query.kc_idp_hint === "bceid") + console.warn("BCeID login is not supported yet."); + return {}; + }, + }); +} + +module.exports = middleware; diff --git a/app/server/postgraphile/authenticationPgSettings.js b/app/server/postgraphile/authenticationPgSettings.js index 3d45343d85..1dd46db3fe 100644 --- a/app/server/postgraphile/authenticationPgSettings.js +++ b/app/server/postgraphile/authenticationPgSettings.js @@ -1,3 +1,4 @@ +const { isAuthenticated } = require("@bcgov-cas/sso-express/dist/helpers"); const { getAllGroups, getPriorityGroup } = require("../../lib/user-groups"); const { getUserGroups } = require("../helpers/userGroupAuthentication"); const groupData = require("../../data/groups"); @@ -66,23 +67,18 @@ const authenticationPgSettings = (req) => { const groups = getUserGroups(req); const priorityGroup = getPriorityGroup(groups); - const claims = { + const claimsSettings = { role: groupData[priorityGroup].pgRole, }; - if ( - !req.kauth || - !req.kauth.grant || - !req.kauth.grant.id_token || - !req.kauth.grant.id_token.content - ) + if (!isAuthenticated(req)) return { - ...claims, + ...claimsSettings, }; - const token = req.kauth.grant.id_token.content; + const { claims } = req; - token.user_groups = groups.join(","); - token.priority_group = priorityGroup; + claims.user_groups = groups.join(","); + claims.priority_group = priorityGroup; const properties = [ "jti", @@ -108,11 +104,11 @@ const authenticationPgSettings = (req) => { "priority_group", ]; properties.forEach((property) => { - claims[`jwt.claims.${property}`] = token[property]; + claimsSettings[`jwt.claims.${property}`] = claims[property]; }); return { - ...claims, + ...claimsSettings, }; }; From 2220feda02824c2ea4b303afd0e3d993dbad3e99 Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Fri, 13 Jan 2023 13:18:21 -0800 Subject: [PATCH 13/35] test: update tests for cif_user table --- schema/test/unit/tables/ciip_user_test.sql | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/schema/test/unit/tables/ciip_user_test.sql b/schema/test/unit/tables/ciip_user_test.sql index 5693754f66..1bc169b85f 100644 --- a/schema/test/unit/tables/ciip_user_test.sql +++ b/schema/test/unit/tables/ciip_user_test.sql @@ -33,14 +33,14 @@ select lives_ok( select lives_ok( $$ - insert into ggircs_portal.ciip_user (uuid, first_name, last_name) values ('11111111-1111-1111-1111-111111111111'::uuid, 'test', 'testerson'); + insert into ggircs_portal.ciip_user (uuid, first_name, last_name) values ('11111111-1111-1111-1111-111111111111', 'test', 'testerson'); $$, 'ciip_administrator can insert data in ciip_user table' ); select lives_ok( $$ - update ggircs_portal.ciip_user set first_name = 'changed by admin' where uuid='11111111-1111-1111-1111-111111111111'::uuid; + update ggircs_portal.ciip_user set first_name = 'changed by admin' where uuid='11111111-1111-1111-1111-111111111111'; $$, 'ciip_administrator can change data in ciip_user table' ); @@ -55,7 +55,7 @@ select results_eq( select throws_like( $$ - update ggircs_portal.ciip_user set uuid = 'ca716545-a8d3-4034-819c-5e45b0e775c9' where uuid = '11111111-1111-1111-1111-111111111111'::uuid; + update ggircs_portal.ciip_user set uuid = 'ca716545-a8d3-4034-819c-5e45b0e775c9' where uuid = '11111111-1111-1111-1111-111111111111'; $$, 'permission denied%', 'ciip_administrator can not change data in the uuid column in ciip_user table' @@ -81,14 +81,6 @@ select results_eq( 'Industry user can view all data from ciip_user' ); --- select results_eq( --- $$ --- select uuid from ggircs_portal.ciip_user --- $$, --- ARRAY['11111111-1111-1111-1111-111111111111'::uuid], --- 'Industry user can view data from ciip_user where the uuid column matches their session uuid' --- ); - select lives_ok( $$ update ggircs_portal.ciip_user set first_name = 'doood' where uuid=(select sub from ggircs_portal.session()) @@ -197,7 +189,7 @@ select results_eq( $$ select uuid from ggircs_portal.ciip_user $$, - ARRAY['11111111-1111-1111-1111-111111111111'::uuid], + ARRAY['11111111-1111-1111-1111-111111111111'::varchar], 'Guest can only select their own user' ); @@ -211,7 +203,7 @@ select throws_like( select throws_like( $$ - insert into ggircs_portal.ciip_user (uuid, first_name, last_name) values ('21111111-1111-1111-1111-111111111111'::uuid, 'test', 'testerson'); + insert into ggircs_portal.ciip_user (uuid, first_name, last_name) values ('21111111-1111-1111-1111-111111111111', 'test', 'testerson'); $$, 'permission denied%', 'Guest cannot insert' From 1f2e626e1c096fab83f24063f7b2b39b638a75b3 Mon Sep 17 00:00:00 2001 From: Pierre Bastianelli Date: Fri, 13 Jan 2023 14:03:11 -0800 Subject: [PATCH 14/35] refactor: removing obsolete registration buttons --- app/components/Layout/Header.tsx | 7 +------ app/components/LoginButton.tsx | 4 ++-- app/containers/RegistrationLoginButtons.tsx | 13 +------------ app/server/middleware/sso.js | 2 +- 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/app/components/Layout/Header.tsx b/app/components/Layout/Header.tsx index 98106397fc..9a829841ba 100644 --- a/app/components/Layout/Header.tsx +++ b/app/components/Layout/Header.tsx @@ -120,15 +120,10 @@ const HeaderLayout: React.FunctionComponent = ({ isRegistered && userProfileDropdown ) : ( <> -
  • - - Register - -
  • diff --git a/app/components/LoginButton.tsx b/app/components/LoginButton.tsx index 3b5a0b088b..9c17371296 100644 --- a/app/components/LoginButton.tsx +++ b/app/components/LoginButton.tsx @@ -4,10 +4,10 @@ import { Form } from "react-bootstrap"; const LoginForm: React.FunctionComponent = ({ children }) => { const router = useRouter(); - let loginURI = "/login"; + let loginURI = "/login?kc_idp_hint=idir"; if (router.query.redirectTo) - loginURI += `?redirectTo=${encodeURIComponent( + loginURI += `&redirectTo=${encodeURIComponent( router.query.redirectTo as string )}`; diff --git a/app/containers/RegistrationLoginButtons.tsx b/app/containers/RegistrationLoginButtons.tsx index 3d91fa8640..4327ff57f2 100644 --- a/app/containers/RegistrationLoginButtons.tsx +++ b/app/containers/RegistrationLoginButtons.tsx @@ -46,20 +46,9 @@ export const RegistrationLoginButtonsComponent: React.FunctionComponent = Apply for the CleanBC Industrial Incentive Program (CIIP) {cardText} - - Register and Apply - - - - +