From 7e17c09112ba149b371a24244aff9b59eb0b56de Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 12 Feb 2024 15:22:00 -0600 Subject: [PATCH 01/26] chore: add database test fixture to insert non-unique linked_ids --- .../fixtures/000189_workspace_app_order.up.sql | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql diff --git a/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql b/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql new file mode 100644 index 0000000000000..3e41155957fe7 --- /dev/null +++ b/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql @@ -0,0 +1,14 @@ +-- This is a deleted user that shares the same username and linked_id as the existing user below. +-- Any future migrations need to handle this case. +INSERT INTO public.users(id, email, username, password, created_at, updated_at, status, rbac_roles, is_deleted) + VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', true) ON CONFLICT DO NOTHING; +INSERT INTO public.organization_members VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; +INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) + VALUES('a0061a8e-7db7-4585-838c-3116a003dd21', 'github', '100', ''); + + +INSERT INTO public.users(id, email, username, password, created_at, updated_at, status, rbac_roles, is_deleted) + VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', false) ON CONFLICT DO NOTHING; +INSERT INTO public.organization_members VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; +INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) + VALUES('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'github', '100', ''); From 4abe5285d5e3277bfbd871aee343ec0fd9d7a57e Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 12 Feb 2024 15:23:18 -0600 Subject: [PATCH 02/26] mend --- .../testdata/fixtures/000189_workspace_app_order.up.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql b/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql index 3e41155957fe7..d3fd503b4b79d 100644 --- a/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql +++ b/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql @@ -1,13 +1,13 @@ -- This is a deleted user that shares the same username and linked_id as the existing user below. -- Any future migrations need to handle this case. -INSERT INTO public.users(id, email, username, password, created_at, updated_at, status, rbac_roles, is_deleted) +INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, is_deleted) VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', true) ON CONFLICT DO NOTHING; INSERT INTO public.organization_members VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) VALUES('a0061a8e-7db7-4585-838c-3116a003dd21', 'github', '100', ''); -INSERT INTO public.users(id, email, username, password, created_at, updated_at, status, rbac_roles, is_deleted) +INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, is_deleted) VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', false) ON CONFLICT DO NOTHING; INSERT INTO public.organization_members VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) From 5fdbf984016ae4eeac428c40912a18b82b8c6590 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 12 Feb 2024 15:25:48 -0600 Subject: [PATCH 03/26] mend --- coderd/database/migrations/migrate_test.go | 2 +- .../testdata/fixtures/000189_workspace_app_order.up.sql | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coderd/database/migrations/migrate_test.go b/coderd/database/migrations/migrate_test.go index c475c1fa5f026..a01ee8e7f3ef3 100644 --- a/coderd/database/migrations/migrate_test.go +++ b/coderd/database/migrations/migrate_test.go @@ -352,7 +352,7 @@ func TestMigrateUpWithFixtures(t *testing.T) { for _, table := range tables { var count int - err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM "+table).Scan(&count) + err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM"+table).Scan(&count) require.NoError(t, err) if tt.useStats { diff --git a/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql b/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql index d3fd503b4b79d..5424c294bd221 100644 --- a/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql +++ b/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql @@ -1,13 +1,13 @@ -- This is a deleted user that shares the same username and linked_id as the existing user below. -- Any future migrations need to handle this case. -INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, is_deleted) +INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, deleted) VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', true) ON CONFLICT DO NOTHING; INSERT INTO public.organization_members VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) VALUES('a0061a8e-7db7-4585-838c-3116a003dd21', 'github', '100', ''); -INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, is_deleted) +INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, deleted) VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', false) ON CONFLICT DO NOTHING; INSERT INTO public.organization_members VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) From 513e1ccf8a3a3a11986492b15ebb7aecd01e3044 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 12 Feb 2024 15:28:41 -0600 Subject: [PATCH 04/26] rename test fixute --- ...kspace_app_order.up.sql => 000189_duplicate_user_links.up.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/testdata/fixtures/{000189_workspace_app_order.up.sql => 000189_duplicate_user_links.up.sql} (100%) diff --git a/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql b/coderd/database/migrations/testdata/fixtures/000189_duplicate_user_links.up.sql similarity index 100% rename from coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql rename to coderd/database/migrations/testdata/fixtures/000189_duplicate_user_links.up.sql From 8435d59ff89de16cad733ce9886140522f7ae6e7 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 12 Feb 2024 15:30:01 -0600 Subject: [PATCH 05/26] add extra case --- .../testdata/fixtures/000189_duplicate_user_links.up.sql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/coderd/database/migrations/testdata/fixtures/000189_duplicate_user_links.up.sql b/coderd/database/migrations/testdata/fixtures/000189_duplicate_user_links.up.sql index 5424c294bd221..0fb1d0efd4aca 100644 --- a/coderd/database/migrations/testdata/fixtures/000189_duplicate_user_links.up.sql +++ b/coderd/database/migrations/testdata/fixtures/000189_duplicate_user_links.up.sql @@ -12,3 +12,8 @@ INSERT INTO public.users(id, email, username, hashed_password, created_at, updat INSERT INTO public.organization_members VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) VALUES('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'github', '100', ''); + +-- Additionally, there is no unique constraint on user_id. So also add another user_link for the same user. +-- This has happened on a production database. +INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) +VALUES('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'oidc', 'foo', ''); From 9e3085d1b35470abf6ef6d7ff31ce52ec30346c4 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 12 Feb 2024 15:33:01 -0600 Subject: [PATCH 06/26] accidental file change --- coderd/database/migrations/migrate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/migrations/migrate_test.go b/coderd/database/migrations/migrate_test.go index a01ee8e7f3ef3..c475c1fa5f026 100644 --- a/coderd/database/migrations/migrate_test.go +++ b/coderd/database/migrations/migrate_test.go @@ -352,7 +352,7 @@ func TestMigrateUpWithFixtures(t *testing.T) { for _, table := range tables { var count int - err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM"+table).Scan(&count) + err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM "+table).Scan(&count) require.NoError(t, err) if tt.useStats { From fea25b05d5d15625994d5637558023955681fe9c Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 12 Feb 2024 15:22:00 -0600 Subject: [PATCH 07/26] chore: add database test fixture to insert non-unique linked_ids --- .../fixtures/000189_workspace_app_order.up.sql | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql diff --git a/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql b/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql new file mode 100644 index 0000000000000..3e41155957fe7 --- /dev/null +++ b/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql @@ -0,0 +1,14 @@ +-- This is a deleted user that shares the same username and linked_id as the existing user below. +-- Any future migrations need to handle this case. +INSERT INTO public.users(id, email, username, password, created_at, updated_at, status, rbac_roles, is_deleted) + VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', true) ON CONFLICT DO NOTHING; +INSERT INTO public.organization_members VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; +INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) + VALUES('a0061a8e-7db7-4585-838c-3116a003dd21', 'github', '100', ''); + + +INSERT INTO public.users(id, email, username, password, created_at, updated_at, status, rbac_roles, is_deleted) + VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', false) ON CONFLICT DO NOTHING; +INSERT INTO public.organization_members VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; +INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) + VALUES('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'github', '100', ''); From 51de1f42bee845573b877e403a3194855c95dec3 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 12 Feb 2024 15:23:18 -0600 Subject: [PATCH 08/26] mend --- .../testdata/fixtures/000189_workspace_app_order.up.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql b/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql index 3e41155957fe7..d3fd503b4b79d 100644 --- a/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql +++ b/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql @@ -1,13 +1,13 @@ -- This is a deleted user that shares the same username and linked_id as the existing user below. -- Any future migrations need to handle this case. -INSERT INTO public.users(id, email, username, password, created_at, updated_at, status, rbac_roles, is_deleted) +INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, is_deleted) VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', true) ON CONFLICT DO NOTHING; INSERT INTO public.organization_members VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) VALUES('a0061a8e-7db7-4585-838c-3116a003dd21', 'github', '100', ''); -INSERT INTO public.users(id, email, username, password, created_at, updated_at, status, rbac_roles, is_deleted) +INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, is_deleted) VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', false) ON CONFLICT DO NOTHING; INSERT INTO public.organization_members VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) From 0e9270bddf63f99ab7a8a3edd289e0deacfd5f52 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 12 Feb 2024 15:25:48 -0600 Subject: [PATCH 09/26] mend --- coderd/database/migrations/migrate_test.go | 2 +- .../testdata/fixtures/000189_workspace_app_order.up.sql | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coderd/database/migrations/migrate_test.go b/coderd/database/migrations/migrate_test.go index c475c1fa5f026..a01ee8e7f3ef3 100644 --- a/coderd/database/migrations/migrate_test.go +++ b/coderd/database/migrations/migrate_test.go @@ -352,7 +352,7 @@ func TestMigrateUpWithFixtures(t *testing.T) { for _, table := range tables { var count int - err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM "+table).Scan(&count) + err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM"+table).Scan(&count) require.NoError(t, err) if tt.useStats { diff --git a/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql b/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql index d3fd503b4b79d..5424c294bd221 100644 --- a/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql +++ b/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql @@ -1,13 +1,13 @@ -- This is a deleted user that shares the same username and linked_id as the existing user below. -- Any future migrations need to handle this case. -INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, is_deleted) +INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, deleted) VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', true) ON CONFLICT DO NOTHING; INSERT INTO public.organization_members VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) VALUES('a0061a8e-7db7-4585-838c-3116a003dd21', 'github', '100', ''); -INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, is_deleted) +INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, deleted) VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', false) ON CONFLICT DO NOTHING; INSERT INTO public.organization_members VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) From 0b80aa5c381f5cf8fcc6c8f7a4dc37700298515c Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 12 Feb 2024 15:28:41 -0600 Subject: [PATCH 10/26] rename test fixute --- .../fixtures/000189_workspace_app_order.up.sql | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql diff --git a/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql b/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql deleted file mode 100644 index 5424c294bd221..0000000000000 --- a/coderd/database/migrations/testdata/fixtures/000189_workspace_app_order.up.sql +++ /dev/null @@ -1,14 +0,0 @@ --- This is a deleted user that shares the same username and linked_id as the existing user below. --- Any future migrations need to handle this case. -INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, deleted) - VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', true) ON CONFLICT DO NOTHING; -INSERT INTO public.organization_members VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; -INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) - VALUES('a0061a8e-7db7-4585-838c-3116a003dd21', 'github', '100', ''); - - -INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, deleted) - VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', false) ON CONFLICT DO NOTHING; -INSERT INTO public.organization_members VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; -INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) - VALUES('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'github', '100', ''); From 7030101d8663527b5a09c37ff9bafd0080710b7e Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 12 Feb 2024 15:33:01 -0600 Subject: [PATCH 11/26] accidental file change --- coderd/database/migrations/migrate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/migrations/migrate_test.go b/coderd/database/migrations/migrate_test.go index a01ee8e7f3ef3..c475c1fa5f026 100644 --- a/coderd/database/migrations/migrate_test.go +++ b/coderd/database/migrations/migrate_test.go @@ -352,7 +352,7 @@ func TestMigrateUpWithFixtures(t *testing.T) { for _, table := range tables { var count int - err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM"+table).Scan(&count) + err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM "+table).Scan(&count) require.NoError(t, err) if tt.useStats { From 4d8993715cd792a3542074dd596ac04e0223aafe Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 12 Feb 2024 15:42:47 -0600 Subject: [PATCH 12/26] chore: creaet unit test to exercise failed email change bug Changing emails on github fails if another deleted user exists with the same link. --- coderd/userauth_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go index f4e12cec8b2cb..ca6c2fb3e9c38 100644 --- a/coderd/userauth_test.go +++ b/coderd/userauth_test.go @@ -605,9 +605,6 @@ func TestUserOAuth2Github(t *testing.T) { }) // The bug only is exercised when a deleted user with the same linked_id exists. - // Still related open issues: - // - https://github.com/coder/coder/issues/12116 - // - https://github.com/coder/coder/issues/12115 t.Run("ChangedEmail", func(t *testing.T) { t.Parallel() From 4628c394a69c92a09d74c956994583207bb247aa Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Feb 2024 07:49:38 -0600 Subject: [PATCH 13/26] Add links to existing issues --- coderd/userauth_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go index ca6c2fb3e9c38..f4e12cec8b2cb 100644 --- a/coderd/userauth_test.go +++ b/coderd/userauth_test.go @@ -605,6 +605,9 @@ func TestUserOAuth2Github(t *testing.T) { }) // The bug only is exercised when a deleted user with the same linked_id exists. + // Still related open issues: + // - https://github.com/coder/coder/issues/12116 + // - https://github.com/coder/coder/issues/12115 t.Run("ChangedEmail", func(t *testing.T) { t.Parallel() From c42625941ac440d66294cf6a7fb76ee7c6bc789d Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Feb 2024 08:01:38 -0600 Subject: [PATCH 14/26] fix: add postgres triggers to keep user_links clear of deleted users --- coderd/database/dump.sql | 29 +++++++- ...190_trigger_delete_user_user_link.down.sql | 26 ++++++++ ...00190_trigger_delete_user_user_link.up.sql | 66 +++++++++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 coderd/database/migrations/000190_trigger_delete_user_user_link.down.sql create mode 100644 coderd/database/migrations/000190_trigger_delete_user_user_link.up.sql diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 07ce232c02032..8daf73e9f0a92 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -187,14 +187,22 @@ CREATE TYPE workspace_transition AS ENUM ( 'delete' ); -CREATE FUNCTION delete_deleted_user_api_keys() RETURNS trigger +CREATE FUNCTION delete_deleted_user_resources() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE BEGIN IF (NEW.deleted) THEN + -- Remove their api_keys DELETE FROM api_keys WHERE user_id = OLD.id; + + -- Remove their user_links + -- Their login_type is preserved in the users table. + -- Matching this user back to the link can still be done by their + -- email if the account is undeleted. Although that is not a guarantee. + DELETE FROM user_links + WHERE user_id = OLD.id; END IF; RETURN NEW; END; @@ -215,6 +223,21 @@ BEGIN END; $$; +CREATE FUNCTION insert_user_links_fail_if_user_deleted() RETURNS trigger + LANGUAGE plpgsql + AS $$ + +DECLARE +BEGIN + IF (NEW.user_id IS NOT NULL) THEN + IF (SELECT deleted FROM users WHERE id = NEW.user_id LIMIT 1) THEN + RAISE EXCEPTION 'Cannot create user_link for deleted user'; + END IF; + END IF; + RETURN NEW; +END; +$$; + CREATE FUNCTION tailnet_notify_agent_change() RETURNS trigger LANGUAGE plpgsql AS $$ @@ -1545,7 +1568,9 @@ CREATE TRIGGER tailnet_notify_tunnel_change AFTER INSERT OR DELETE OR UPDATE ON CREATE TRIGGER trigger_insert_apikeys BEFORE INSERT ON api_keys FOR EACH ROW EXECUTE FUNCTION insert_apikey_fail_if_user_deleted(); -CREATE TRIGGER trigger_update_users AFTER INSERT OR UPDATE ON users FOR EACH ROW WHEN ((new.deleted = true)) EXECUTE FUNCTION delete_deleted_user_api_keys(); +CREATE TRIGGER trigger_update_users AFTER INSERT OR UPDATE ON users FOR EACH ROW WHEN ((new.deleted = true)) EXECUTE FUNCTION delete_deleted_user_resources(); + +CREATE TRIGGER trigger_upsert_user_links BEFORE INSERT OR UPDATE ON user_links FOR EACH ROW EXECUTE FUNCTION insert_user_links_fail_if_user_deleted(); ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; diff --git a/coderd/database/migrations/000190_trigger_delete_user_user_link.down.sql b/coderd/database/migrations/000190_trigger_delete_user_user_link.down.sql new file mode 100644 index 0000000000000..836a587671281 --- /dev/null +++ b/coderd/database/migrations/000190_trigger_delete_user_user_link.down.sql @@ -0,0 +1,26 @@ +DROP TRIGGER IF EXISTS trigger_update_users ON users; +DROP FUNCTION IF EXISTS delete_deleted_user_resources; + +DROP TRIGGER IF EXISTS trigger_upsert_user_links ON user_links; +DROP FUNCTION IF EXISTS insert_user_links_fail_if_user_deleted; + +-- Restore the previous trigger +CREATE FUNCTION delete_deleted_user_api_keys() RETURNS trigger + LANGUAGE plpgsql +AS $$ +DECLARE +BEGIN + IF (NEW.deleted) THEN + DELETE FROM api_keys + WHERE user_id = OLD.id; + END IF; + RETURN NEW; +END; +$$; + + +CREATE TRIGGER trigger_update_users + AFTER INSERT OR UPDATE ON users + FOR EACH ROW + WHEN (NEW.deleted = true) +EXECUTE PROCEDURE delete_deleted_user_api_keys(); diff --git a/coderd/database/migrations/000190_trigger_delete_user_user_link.up.sql b/coderd/database/migrations/000190_trigger_delete_user_user_link.up.sql new file mode 100644 index 0000000000000..90f4148dc63dc --- /dev/null +++ b/coderd/database/migrations/000190_trigger_delete_user_user_link.up.sql @@ -0,0 +1,66 @@ +-- We need to delete all existing user_links for soft-deleted users +DELETE FROM + user_links +WHERE + user_id + IN ( + SELECT id FROM users WHERE deleted + ); + +-- Drop the old trigger +DROP TRIGGER trigger_update_users ON users; +-- Drop the old function +DROP FUNCTION delete_deleted_user_api_keys; + +-- When we soft-delete a user, we also want to delete their API key. +-- The previous function deleted all api keys. This extends that with user_links. +CREATE FUNCTION delete_deleted_user_resources() RETURNS trigger + LANGUAGE plpgsql +AS $$ +DECLARE +BEGIN + IF (NEW.deleted) THEN + -- Remove their api_keys + DELETE FROM api_keys + WHERE user_id = OLD.id; + + -- Remove their user_links + -- Their login_type is preserved in the users table. + -- Matching this user back to the link can still be done by their + -- email if the account is undeleted. Although that is not a guarantee. + DELETE FROM user_links + WHERE user_id = OLD.id; + END IF; + RETURN NEW; +END; +$$; + + +-- Update it to the new trigger +CREATE TRIGGER trigger_update_users + AFTER INSERT OR UPDATE ON users + FOR EACH ROW + WHEN (NEW.deleted = true) +EXECUTE PROCEDURE delete_deleted_user_resources(); + + +-- Prevent adding new user_links for soft-deleted users +CREATE FUNCTION insert_user_links_fail_if_user_deleted() RETURNS trigger + LANGUAGE plpgsql +AS $$ + +DECLARE +BEGIN + IF (NEW.user_id IS NOT NULL) THEN + IF (SELECT deleted FROM users WHERE id = NEW.user_id LIMIT 1) THEN + RAISE EXCEPTION 'Cannot create user_link for deleted user'; + END IF; + END IF; + RETURN NEW; +END; +$$; + +CREATE TRIGGER trigger_upsert_user_links + BEFORE INSERT OR UPDATE ON user_links + FOR EACH ROW +EXECUTE PROCEDURE insert_user_links_fail_if_user_deleted(); From 188f03ba50a1d0265b6198461fde655171a6f990 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Feb 2024 09:03:30 -0600 Subject: [PATCH 15/26] Add migrations to prevent deleted users with links --- coderd/database/dbmem/dbmem.go | 43 +++++++++++++------ ...91_trigger_delete_user_user_link.down.sql} | 0 ...0191_trigger_delete_user_user_link.up.sql} | 0 coderd/userauth_test.go | 25 ++++++++++- 4 files changed, 52 insertions(+), 16 deletions(-) rename coderd/database/migrations/{000190_trigger_delete_user_user_link.down.sql => 000191_trigger_delete_user_user_link.down.sql} (100%) rename coderd/database/migrations/{000190_trigger_delete_user_user_link.up.sql => 000191_trigger_delete_user_user_link.up.sql} (100%) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 0e77bacd3b216..8cb53453e3e67 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -5573,10 +5573,26 @@ func (q *FakeQuerier) InsertUserGroupsByName(_ context.Context, arg database.Ins return nil } +// Took the error from the real database. +var deletedUserLinkError = &pq.Error{ + Severity: "ERROR", + // "raise_exception" error + Code: "P0001", + Message: "Cannot create user_link for deleted user", + Where: "PL/pgSQL function insert_user_links_fail_if_user_deleted() line 7 at RAISE", + File: "pl_exec.c", + Line: "3864", + Routine: "exec_stmt_raise", +} + func (q *FakeQuerier) InsertUserLink(_ context.Context, args database.InsertUserLinkParams) (database.UserLink, error) { q.mutex.Lock() defer q.mutex.Unlock() + if u, err := q.getUserByIDNoLock(args.UserID); err == nil && u.Deleted { + return database.UserLink{}, deletedUserLinkError + } + //nolint:gosimple link := database.UserLink{ UserID: args.UserID, @@ -6736,20 +6752,15 @@ func (q *FakeQuerier) UpdateUserDeletedByID(_ context.Context, params database.U if u.ID == params.ID { u.Deleted = params.Deleted q.users[i] = u - // NOTE: In the real world, this is done by a trigger. - i := 0 - for { - if i >= len(q.apiKeys) { - break - } - k := q.apiKeys[i] - if k.UserID == u.ID { - q.apiKeys[i] = q.apiKeys[len(q.apiKeys)-1] - q.apiKeys = q.apiKeys[:len(q.apiKeys)-1] - // We removed an element, so decrement - i-- - } - i++ + if params.Deleted { + // NOTE: In the real world, this is done by a trigger. + q.apiKeys = slices.DeleteFunc(q.apiKeys, func(u database.APIKey) bool { + return params.ID == u.UserID + }) + + q.userLinks = slices.DeleteFunc(q.userLinks, func(u database.UserLink) bool { + return params.ID == u.UserID + }) } return nil } @@ -6804,6 +6815,10 @@ func (q *FakeQuerier) UpdateUserLink(_ context.Context, params database.UpdateUs q.mutex.Lock() defer q.mutex.Unlock() + if u, err := q.getUserByIDNoLock(params.UserID); err == nil && u.Deleted { + return database.UserLink{}, deletedUserLinkError + } + for i, link := range q.userLinks { if link.UserID == params.UserID && link.LoginType == params.LoginType { link.OAuthAccessToken = params.OAuthAccessToken diff --git a/coderd/database/migrations/000190_trigger_delete_user_user_link.down.sql b/coderd/database/migrations/000191_trigger_delete_user_user_link.down.sql similarity index 100% rename from coderd/database/migrations/000190_trigger_delete_user_user_link.down.sql rename to coderd/database/migrations/000191_trigger_delete_user_user_link.down.sql diff --git a/coderd/database/migrations/000190_trigger_delete_user_user_link.up.sql b/coderd/database/migrations/000191_trigger_delete_user_user_link.up.sql similarity index 100% rename from coderd/database/migrations/000190_trigger_delete_user_user_link.up.sql rename to coderd/database/migrations/000191_trigger_delete_user_user_link.up.sql diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go index f4e12cec8b2cb..9e2f52b9edc88 100644 --- a/coderd/userauth_test.go +++ b/coderd/userauth_test.go @@ -9,6 +9,7 @@ import ( "net/url" "strings" "testing" + "time" "github.com/coreos/go-oidc/v3/oidc" "github.com/golang-jwt/jwt/v4" @@ -24,6 +25,7 @@ import ( "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/coderdtest/oidctest" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbgen" "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/coderd/promoauth" @@ -632,7 +634,7 @@ func TestUserOAuth2Github(t *testing.T) { coderEmail, } - owner := coderdtest.New(t, &coderdtest.Options{ + owner, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{ Auditor: auditor, GithubOAuth2Config: &coderd.GithubOAuth2Config{ AllowSignups: true, @@ -655,9 +657,12 @@ func TestUserOAuth2Github(t *testing.T) { }, }, }) - coderdtest.CreateFirstUser(t, owner) + first := coderdtest.CreateFirstUser(t, owner) ctx := testutil.Context(t, testutil.WaitLong) + ownerUser, err := owner.User(context.Background(), "me") + require.NoError(t, err) + // Create the user, then delete the user, then create again. // This causes the email change to fail. client := codersdk.New(owner.URL) @@ -668,6 +673,22 @@ func TestUserOAuth2Github(t *testing.T) { err = owner.DeleteUser(ctx, deleted.ID) require.NoError(t, err) + // Check no user links for the user + links, err := db.GetUserLinksByUserID(dbauthz.As(ctx, coderdtest.AuthzUserSubject(ownerUser, first.OrganizationID)), deleted.ID) + require.NoError(t, err) + require.Empty(t, links) + + // Make sure a user_link cannot be created with a deleted user. + _, err = db.InsertUserLink(dbauthz.AsSystemRestricted(ctx), database.InsertUserLinkParams{ + UserID: deleted.ID, + LoginType: "github", + LinkedID: "100", + OAuthAccessToken: "random", + OAuthRefreshToken: "random", + OAuthExpiry: time.Now(), + DebugContext: []byte(`{}`), + }) + require.ErrorContains(t, err, "Cannot create user_link for deleted user") // Create the user again. client, _ = fake.Login(t, client, jwt.MapClaims{}) From e0902a38319e213309edbe2d37868055b3e7dac9 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Feb 2024 09:43:46 -0600 Subject: [PATCH 16/26] Force soft delete of users, do not allow un-delete --- coderd/database/dbauthz/dbauthz.go | 26 ++------- coderd/database/dbmem/dbmem.go | 74 ++++++++++++-------------- coderd/database/dbmetrics/dbmetrics.go | 14 ++--- coderd/database/dbmock/dbmock.go | 28 +++++----- coderd/database/querier.go | 2 +- coderd/database/queries.sql.go | 33 +++++------- coderd/database/queries/users.sql | 4 +- coderd/users.go | 5 +- 8 files changed, 77 insertions(+), 109 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index a0da90eb52f23..1f98881eee278 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -607,16 +607,6 @@ func (q *querier) SoftDeleteTemplateByID(ctx context.Context, id uuid.UUID) erro return deleteQ(q.log, q.auth, q.db.GetTemplateByID, deleteF)(ctx, id) } -func (q *querier) SoftDeleteUserByID(ctx context.Context, id uuid.UUID) error { - deleteF := func(ctx context.Context, id uuid.UUID) error { - return q.db.UpdateUserDeletedByID(ctx, database.UpdateUserDeletedByIDParams{ - ID: id, - Deleted: true, - }) - } - return deleteQ(q.log, q.auth, q.db.GetUserByID, deleteF)(ctx, id) -} - func (q *querier) SoftDeleteWorkspaceByID(ctx context.Context, id uuid.UUID) error { return deleteQ(q.log, q.auth, q.db.GetWorkspaceByID, func(ctx context.Context, id uuid.UUID) error { return q.db.UpdateWorkspaceDeletedByID(ctx, database.UpdateWorkspaceDeletedByIDParams{ @@ -2556,6 +2546,10 @@ func (q *querier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) return q.db.RevokeDBCryptKey(ctx, activeKeyDigest) } +func (q *querier) SoftDeleteUserByID(ctx context.Context, id uuid.UUID) error { + return deleteQ(q.log, q.auth, q.db.GetUserByID, q.db.SoftDeleteUserByID)(ctx, id) +} + func (q *querier) TryAcquireLock(ctx context.Context, id int64) (bool, error) { return q.db.TryAcquireLock(ctx, id) } @@ -2877,18 +2871,6 @@ func (q *querier) UpdateUserAppearanceSettings(ctx context.Context, arg database return q.db.UpdateUserAppearanceSettings(ctx, arg) } -// UpdateUserDeletedByID -// Deprecated: Delete this function in favor of 'SoftDeleteUserByID'. Deletes are -// irreversible. -func (q *querier) UpdateUserDeletedByID(ctx context.Context, arg database.UpdateUserDeletedByIDParams) error { - fetch := func(ctx context.Context, arg database.UpdateUserDeletedByIDParams) (database.User, error) { - return q.db.GetUserByID(ctx, arg.ID) - } - // This uses the rbac.ActionDelete action always as this function should always delete. - // We should delete this function in favor of 'SoftDeleteUserByID'. - return deleteQ(q.log, q.auth, fetch, q.db.UpdateUserDeletedByID)(ctx, arg) -} - func (q *querier) UpdateUserHashedPassword(ctx context.Context, arg database.UpdateUserHashedPasswordParams) error { user, err := q.db.GetUserByID(ctx, arg.ID) if err != nil { diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 8cb53453e3e67..b43856d9f65b5 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -5573,18 +5573,6 @@ func (q *FakeQuerier) InsertUserGroupsByName(_ context.Context, arg database.Ins return nil } -// Took the error from the real database. -var deletedUserLinkError = &pq.Error{ - Severity: "ERROR", - // "raise_exception" error - Code: "P0001", - Message: "Cannot create user_link for deleted user", - Where: "PL/pgSQL function insert_user_links_fail_if_user_deleted() line 7 at RAISE", - File: "pl_exec.c", - Line: "3864", - Routine: "exec_stmt_raise", -} - func (q *FakeQuerier) InsertUserLink(_ context.Context, args database.InsertUserLinkParams) (database.UserLink, error) { q.mutex.Lock() defer q.mutex.Unlock() @@ -6138,6 +6126,40 @@ func (q *FakeQuerier) RevokeDBCryptKey(_ context.Context, activeKeyDigest string return sql.ErrNoRows } +// Took the error from the real database. +var deletedUserLinkError = &pq.Error{ + Severity: "ERROR", + // "raise_exception" error + Code: "P0001", + Message: "Cannot create user_link for deleted user", + Where: "PL/pgSQL function insert_user_links_fail_if_user_deleted() line 7 at RAISE", + File: "pl_exec.c", + Line: "3864", + Routine: "exec_stmt_raise", +} + +func (q *FakeQuerier) SoftDeleteUserByID(ctx context.Context, id uuid.UUID) error { + q.mutex.Lock() + defer q.mutex.Unlock() + + for i, u := range q.users { + if u.ID == id { + u.Deleted = true + q.users[i] = u + // NOTE: In the real world, this is done by a trigger. + q.apiKeys = slices.DeleteFunc(q.apiKeys, func(u database.APIKey) bool { + return id == u.UserID + }) + + q.userLinks = slices.DeleteFunc(q.userLinks, func(u database.UserLink) bool { + return id == u.UserID + }) + return nil + } + } + return sql.ErrNoRows +} + func (*FakeQuerier) TryAcquireLock(_ context.Context, _ int64) (bool, error) { return false, xerrors.New("TryAcquireLock must only be called within a transaction") } @@ -6740,34 +6762,6 @@ func (q *FakeQuerier) UpdateUserAppearanceSettings(_ context.Context, arg databa return database.User{}, sql.ErrNoRows } -func (q *FakeQuerier) UpdateUserDeletedByID(_ context.Context, params database.UpdateUserDeletedByIDParams) error { - if err := validateDatabaseType(params); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, u := range q.users { - if u.ID == params.ID { - u.Deleted = params.Deleted - q.users[i] = u - if params.Deleted { - // NOTE: In the real world, this is done by a trigger. - q.apiKeys = slices.DeleteFunc(q.apiKeys, func(u database.APIKey) bool { - return params.ID == u.UserID - }) - - q.userLinks = slices.DeleteFunc(q.userLinks, func(u database.UserLink) bool { - return params.ID == u.UserID - }) - } - return nil - } - } - return sql.ErrNoRows -} - func (q *FakeQuerier) UpdateUserHashedPassword(_ context.Context, arg database.UpdateUserHashedPasswordParams) error { if err := validateDatabaseType(arg); err != nil { return err diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index 948de3c763277..a87fe7b8dc416 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -1642,6 +1642,13 @@ func (m metricsStore) RevokeDBCryptKey(ctx context.Context, activeKeyDigest stri return r0 } +func (m metricsStore) SoftDeleteUserByID(ctx context.Context, id uuid.UUID) error { + start := time.Now() + r0 := m.s.SoftDeleteUserByID(ctx, id) + m.queryLatencies.WithLabelValues("SoftDeleteUserByID").Observe(time.Since(start).Seconds()) + return r0 +} + func (m metricsStore) TryAcquireLock(ctx context.Context, pgTryAdvisoryXactLock int64) (bool, error) { start := time.Now() ok, err := m.s.TryAcquireLock(ctx, pgTryAdvisoryXactLock) @@ -1831,13 +1838,6 @@ func (m metricsStore) UpdateUserAppearanceSettings(ctx context.Context, arg data return r0, r1 } -func (m metricsStore) UpdateUserDeletedByID(ctx context.Context, arg database.UpdateUserDeletedByIDParams) error { - start := time.Now() - err := m.s.UpdateUserDeletedByID(ctx, arg) - m.queryLatencies.WithLabelValues("UpdateUserDeletedByID").Observe(time.Since(start).Seconds()) - return err -} - func (m metricsStore) UpdateUserHashedPassword(ctx context.Context, arg database.UpdateUserHashedPasswordParams) error { start := time.Now() err := m.s.UpdateUserHashedPassword(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index d767fd7cf5bd7..b59a0b38cac3d 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -3469,6 +3469,20 @@ func (mr *MockStoreMockRecorder) RevokeDBCryptKey(arg0, arg1 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeDBCryptKey", reflect.TypeOf((*MockStore)(nil).RevokeDBCryptKey), arg0, arg1) } +// SoftDeleteUserByID mocks base method. +func (m *MockStore) SoftDeleteUserByID(arg0 context.Context, arg1 uuid.UUID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SoftDeleteUserByID", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SoftDeleteUserByID indicates an expected call of SoftDeleteUserByID. +func (mr *MockStoreMockRecorder) SoftDeleteUserByID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SoftDeleteUserByID", reflect.TypeOf((*MockStore)(nil).SoftDeleteUserByID), arg0, arg1) +} + // TryAcquireLock mocks base method. func (m *MockStore) TryAcquireLock(arg0 context.Context, arg1 int64) (bool, error) { m.ctrl.T.Helper() @@ -3857,20 +3871,6 @@ func (mr *MockStoreMockRecorder) UpdateUserAppearanceSettings(arg0, arg1 any) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserAppearanceSettings", reflect.TypeOf((*MockStore)(nil).UpdateUserAppearanceSettings), arg0, arg1) } -// UpdateUserDeletedByID mocks base method. -func (m *MockStore) UpdateUserDeletedByID(arg0 context.Context, arg1 database.UpdateUserDeletedByIDParams) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateUserDeletedByID", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateUserDeletedByID indicates an expected call of UpdateUserDeletedByID. -func (mr *MockStoreMockRecorder) UpdateUserDeletedByID(arg0, arg1 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserDeletedByID", reflect.TypeOf((*MockStore)(nil).UpdateUserDeletedByID), arg0, arg1) -} - // UpdateUserHashedPassword mocks base method. func (m *MockStore) UpdateUserHashedPassword(arg0 context.Context, arg1 database.UpdateUserHashedPasswordParams) error { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index cbeb5b1caf746..cf32a14ede7fb 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -322,6 +322,7 @@ type sqlcQuerier interface { ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgentPortShare, error) RegisterWorkspaceProxy(ctx context.Context, arg RegisterWorkspaceProxyParams) (WorkspaceProxy, error) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error + SoftDeleteUserByID(ctx context.Context, id uuid.UUID) error // Non blocking lock. Returns true if the lock was acquired, false otherwise. // // This must be called from within a transaction. The lock will be automatically @@ -354,7 +355,6 @@ type sqlcQuerier interface { UpdateTemplateVersionExternalAuthProvidersByJobID(ctx context.Context, arg UpdateTemplateVersionExternalAuthProvidersByJobIDParams) error UpdateTemplateWorkspacesLastUsedAt(ctx context.Context, arg UpdateTemplateWorkspacesLastUsedAtParams) error UpdateUserAppearanceSettings(ctx context.Context, arg UpdateUserAppearanceSettingsParams) (User, error) - UpdateUserDeletedByID(ctx context.Context, arg UpdateUserDeletedByIDParams) error UpdateUserHashedPassword(ctx context.Context, arg UpdateUserHashedPasswordParams) error UpdateUserLastSeenAt(ctx context.Context, arg UpdateUserLastSeenAtParams) (User, error) UpdateUserLink(ctx context.Context, arg UpdateUserLinkParams) (UserLink, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index f2ef7b697ed00..423870a6c0edb 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -7779,6 +7779,20 @@ func (q *sqlQuerier) InsertUser(ctx context.Context, arg InsertUserParams) (User return i, err } +const softDeleteUserByID = `-- name: SoftDeleteUserByID :exec +UPDATE + users +SET + deleted = true +WHERE + id = $1 +` + +func (q *sqlQuerier) SoftDeleteUserByID(ctx context.Context, id uuid.UUID) error { + _, err := q.db.ExecContext(ctx, softDeleteUserByID, id) + return err +} + const updateInactiveUsersToDormant = `-- name: UpdateInactiveUsersToDormant :many UPDATE users @@ -7865,25 +7879,6 @@ func (q *sqlQuerier) UpdateUserAppearanceSettings(ctx context.Context, arg Updat return i, err } -const updateUserDeletedByID = `-- name: UpdateUserDeletedByID :exec -UPDATE - users -SET - deleted = $2 -WHERE - id = $1 -` - -type UpdateUserDeletedByIDParams struct { - ID uuid.UUID `db:"id" json:"id"` - Deleted bool `db:"deleted" json:"deleted"` -} - -func (q *sqlQuerier) UpdateUserDeletedByID(ctx context.Context, arg UpdateUserDeletedByIDParams) error { - _, err := q.db.ExecContext(ctx, updateUserDeletedByID, arg.ID, arg.Deleted) - return err -} - const updateUserHashedPassword = `-- name: UpdateUserHashedPassword :exec UPDATE users diff --git a/coderd/database/queries/users.sql b/coderd/database/queries/users.sql index 80fe137142da0..5341671586bc0 100644 --- a/coderd/database/queries/users.sql +++ b/coderd/database/queries/users.sql @@ -112,11 +112,11 @@ SET WHERE id = $1; --- name: UpdateUserDeletedByID :exec +-- name: SoftDeleteUserByID :exec UPDATE users SET - deleted = $2 + deleted = true WHERE id = $1; diff --git a/coderd/users.go b/coderd/users.go index ca757ed80436f..d56fe749661ce 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -521,10 +521,7 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) { return } - err = api.Database.UpdateUserDeletedByID(ctx, database.UpdateUserDeletedByIDParams{ - ID: user.ID, - Deleted: true, - }) + err = api.Database.SoftDeleteUserByID(ctx, user.ID) if dbauthz.IsNotAuthorizedError(err) { httpapi.Forbidden(rw) return From e3788d55bd1caf0e8ac67aa699d63faa1e3db56e Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Feb 2024 09:49:14 -0600 Subject: [PATCH 17/26] fix some tests --- cli/delete_test.go | 6 +----- coderd/database/dbauthz/dbauthz_test.go | 7 ------- coderd/database/dbgen/dbgen.go | 5 +---- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/cli/delete_test.go b/cli/delete_test.go index a44cd6e5b2e3c..ba9004cb5828b 100644 --- a/cli/delete_test.go +++ b/cli/delete_test.go @@ -11,7 +11,6 @@ import ( "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/coderd/coderdtest" - "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/pty/ptytest" @@ -95,10 +94,7 @@ func TestDelete(t *testing.T) { // this way. ctx := testutil.Context(t, testutil.WaitShort) // nolint:gocritic // Unit test - err := api.Database.UpdateUserDeletedByID(dbauthz.AsSystemRestricted(ctx), database.UpdateUserDeletedByIDParams{ - ID: deleteMeUser.ID, - Deleted: true, - }) + err := api.Database.SoftDeleteUserByID(dbauthz.AsSystemRestricted(ctx), deleteMeUser.ID) require.NoError(t, err) inv, root := clitest.New(t, "delete", fmt.Sprintf("%s/%s", deleteMeUser.ID, workspace.Name), "-y", "--orphan") diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 56b6012ba2193..ba5c50cc64b96 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -1018,13 +1018,6 @@ func (s *MethodTestSuite) TestUser() { u := dbgen.User(s.T(), db, database.User{}) check.Args(u.ID).Asserts(u, rbac.ActionDelete).Returns() })) - s.Run("UpdateUserDeletedByID", s.Subtest(func(db database.Store, check *expects) { - u := dbgen.User(s.T(), db, database.User{Deleted: true}) - check.Args(database.UpdateUserDeletedByIDParams{ - ID: u.ID, - Deleted: true, - }).Asserts(u, rbac.ActionDelete).Returns() - })) s.Run("UpdateUserHashedPassword", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) check.Args(database.UpdateUserHashedPasswordParams{ diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 0c11a511cb56f..24fb18f99594b 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -310,10 +310,7 @@ func User(t testing.TB, db database.Store, orig database.User) database.User { } if orig.Deleted { - err = db.UpdateUserDeletedByID(genCtx, database.UpdateUserDeletedByIDParams{ - ID: user.ID, - Deleted: orig.Deleted, - }) + err = db.SoftDeleteUserByID(genCtx, user.ID) require.NoError(t, err, "set user as deleted") } return user From ee6aabcaf54184a17a615c2deeec4f0ae6403f68 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Feb 2024 09:50:34 -0600 Subject: [PATCH 18/26] move code --- coderd/database/dbmem/dbmem.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index b43856d9f65b5..d48c05ea75738 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -5573,6 +5573,18 @@ func (q *FakeQuerier) InsertUserGroupsByName(_ context.Context, arg database.Ins return nil } +// Took the error from the real database. +var deletedUserLinkError = &pq.Error{ + Severity: "ERROR", + // "raise_exception" error + Code: "P0001", + Message: "Cannot create user_link for deleted user", + Where: "PL/pgSQL function insert_user_links_fail_if_user_deleted() line 7 at RAISE", + File: "pl_exec.c", + Line: "3864", + Routine: "exec_stmt_raise", +} + func (q *FakeQuerier) InsertUserLink(_ context.Context, args database.InsertUserLinkParams) (database.UserLink, error) { q.mutex.Lock() defer q.mutex.Unlock() @@ -6126,18 +6138,6 @@ func (q *FakeQuerier) RevokeDBCryptKey(_ context.Context, activeKeyDigest string return sql.ErrNoRows } -// Took the error from the real database. -var deletedUserLinkError = &pq.Error{ - Severity: "ERROR", - // "raise_exception" error - Code: "P0001", - Message: "Cannot create user_link for deleted user", - Where: "PL/pgSQL function insert_user_links_fail_if_user_deleted() line 7 at RAISE", - File: "pl_exec.c", - Line: "3864", - Routine: "exec_stmt_raise", -} - func (q *FakeQuerier) SoftDeleteUserByID(ctx context.Context, id uuid.UUID) error { q.mutex.Lock() defer q.mutex.Unlock() From f2c90c19dcc38bf05d3f2cb1192e71b7f02cb592 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Feb 2024 09:56:38 -0600 Subject: [PATCH 19/26] linting --- coderd/database/dbmem/dbmem.go | 2 +- coderd/userauth_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index d48c05ea75738..66028349153fe 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -6138,7 +6138,7 @@ func (q *FakeQuerier) RevokeDBCryptKey(_ context.Context, activeKeyDigest string return sql.ErrNoRows } -func (q *FakeQuerier) SoftDeleteUserByID(ctx context.Context, id uuid.UUID) error { +func (q *FakeQuerier) SoftDeleteUserByID(_ context.Context, id uuid.UUID) error { q.mutex.Lock() defer q.mutex.Unlock() diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go index 9e2f52b9edc88..db23432440e44 100644 --- a/coderd/userauth_test.go +++ b/coderd/userauth_test.go @@ -679,6 +679,7 @@ func TestUserOAuth2Github(t *testing.T) { require.Empty(t, links) // Make sure a user_link cannot be created with a deleted user. + // nolint:gocritic // Unit test _, err = db.InsertUserLink(dbauthz.AsSystemRestricted(ctx), database.InsertUserLinkParams{ UserID: deleted.ID, LoginType: "github", From c6ff41738d115a6e30000892fbf6c57761d7f73f Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Feb 2024 10:02:36 -0600 Subject: [PATCH 20/26] move migration --- ...ink.down.sql => 000192_trigger_delete_user_user_link.down.sql} | 0 ...er_link.up.sql => 000192_trigger_delete_user_user_link.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000191_trigger_delete_user_user_link.down.sql => 000192_trigger_delete_user_user_link.down.sql} (100%) rename coderd/database/migrations/{000191_trigger_delete_user_user_link.up.sql => 000192_trigger_delete_user_user_link.up.sql} (100%) diff --git a/coderd/database/migrations/000191_trigger_delete_user_user_link.down.sql b/coderd/database/migrations/000192_trigger_delete_user_user_link.down.sql similarity index 100% rename from coderd/database/migrations/000191_trigger_delete_user_user_link.down.sql rename to coderd/database/migrations/000192_trigger_delete_user_user_link.down.sql diff --git a/coderd/database/migrations/000191_trigger_delete_user_user_link.up.sql b/coderd/database/migrations/000192_trigger_delete_user_user_link.up.sql similarity index 100% rename from coderd/database/migrations/000191_trigger_delete_user_user_link.up.sql rename to coderd/database/migrations/000192_trigger_delete_user_user_link.up.sql From fa3401e2568fd35314ce4b5b7ee97d03794cb7f8 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Feb 2024 10:08:30 -0600 Subject: [PATCH 21/26] Make gen --- coderd/database/dbmem/dbmem.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 66028349153fe..ce7d496d6895d 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -733,6 +733,18 @@ func isNotNull(v interface{}) bool { return reflect.ValueOf(v).FieldByName("Valid").Bool() } +// Took the error from the real database. +var deletedUserLinkError = &pq.Error{ + Severity: "ERROR", + // "raise_exception" error + Code: "P0001", + Message: "Cannot create user_link for deleted user", + Where: "PL/pgSQL function insert_user_links_fail_if_user_deleted() line 7 at RAISE", + File: "pl_exec.c", + Line: "3864", + Routine: "exec_stmt_raise", +} + func (*FakeQuerier) AcquireLock(_ context.Context, _ int64) error { return xerrors.New("AcquireLock must only be called within a transaction") } @@ -5573,18 +5585,6 @@ func (q *FakeQuerier) InsertUserGroupsByName(_ context.Context, arg database.Ins return nil } -// Took the error from the real database. -var deletedUserLinkError = &pq.Error{ - Severity: "ERROR", - // "raise_exception" error - Code: "P0001", - Message: "Cannot create user_link for deleted user", - Where: "PL/pgSQL function insert_user_links_fail_if_user_deleted() line 7 at RAISE", - File: "pl_exec.c", - Line: "3864", - Routine: "exec_stmt_raise", -} - func (q *FakeQuerier) InsertUserLink(_ context.Context, args database.InsertUserLinkParams) (database.UserLink, error) { q.mutex.Lock() defer q.mutex.Unlock() From a54b652da54167ddfc52be67f80fca6cf3488a57 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Feb 2024 10:20:22 -0600 Subject: [PATCH 22/26] fix user gen --- enterprise/cli/server_dbcrypt_test.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/enterprise/cli/server_dbcrypt_test.go b/enterprise/cli/server_dbcrypt_test.go index e9e88c49d28e1..6bf4dd32ff555 100644 --- a/enterprise/cli/server_dbcrypt_test.go +++ b/enterprise/cli/server_dbcrypt_test.go @@ -236,15 +236,18 @@ func genData(t *testing.T, db database.Store) []database.User { OAuthAccessToken: "access-" + usr.ID.String(), OAuthRefreshToken: "refresh-" + usr.ID.String(), }) - // Fun fact: our schema allows _all_ login types to have - // a user_link. Even though I'm not sure how it could occur - // in practice, making sure to test all combinations here. - _ = dbgen.UserLink(t, db, database.UserLink{ - UserID: usr.ID, - LoginType: usr.LoginType, - OAuthAccessToken: "access-" + usr.ID.String(), - OAuthRefreshToken: "refresh-" + usr.ID.String(), - }) + // Deleted users cannot have user_links + if !deleted { + // Fun fact: our schema allows _all_ login types to have + // a user_link. Even though I'm not sure how it could occur + // in practice, making sure to test all combinations here. + _ = dbgen.UserLink(t, db, database.UserLink{ + UserID: usr.ID, + LoginType: usr.LoginType, + OAuthAccessToken: "access-" + usr.ID.String(), + OAuthRefreshToken: "refresh-" + usr.ID.String(), + }) + } users = append(users, usr) } } From b70a8c953352dd190a4e7dec10edd55e0f2249ad Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Feb 2024 13:41:58 -0600 Subject: [PATCH 23/26] remove excess fixture file --- .../000189_duplicate_user_links.up.sql | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 coderd/database/migrations/testdata/fixtures/000189_duplicate_user_links.up.sql diff --git a/coderd/database/migrations/testdata/fixtures/000189_duplicate_user_links.up.sql b/coderd/database/migrations/testdata/fixtures/000189_duplicate_user_links.up.sql deleted file mode 100644 index 0fb1d0efd4aca..0000000000000 --- a/coderd/database/migrations/testdata/fixtures/000189_duplicate_user_links.up.sql +++ /dev/null @@ -1,19 +0,0 @@ --- This is a deleted user that shares the same username and linked_id as the existing user below. --- Any future migrations need to handle this case. -INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, deleted) - VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', true) ON CONFLICT DO NOTHING; -INSERT INTO public.organization_members VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; -INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) - VALUES('a0061a8e-7db7-4585-838c-3116a003dd21', 'github', '100', ''); - - -INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, deleted) - VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'githubuser@coder.com', 'githubuser', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', false) ON CONFLICT DO NOTHING; -INSERT INTO public.organization_members VALUES ('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; -INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) - VALUES('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'github', '100', ''); - --- Additionally, there is no unique constraint on user_id. So also add another user_link for the same user. --- This has happened on a production database. -INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) -VALUES('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'oidc', 'foo', ''); From f8b63ca3c5df593d83c85a0b06ee21170f44b3c1 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 20 Feb 2024 12:31:39 -0600 Subject: [PATCH 24/26] rename SoftDelete back to UpdateDeleted --- cli/delete_test.go | 2 +- coderd/database/dbauthz/dbauthz.go | 10 ++++-- coderd/database/dbauthz/dbauthz_test.go | 2 +- coderd/database/dbgen/dbgen.go | 2 +- coderd/database/dbmem/dbmem.go | 44 ++++++++++++------------- coderd/database/dbmetrics/dbmetrics.go | 14 ++++---- coderd/database/dbmock/dbmock.go | 28 ++++++++-------- coderd/database/querier.go | 2 +- coderd/database/queries.sql.go | 28 ++++++++-------- coderd/database/queries/users.sql | 2 +- coderd/users.go | 2 +- 11 files changed, 70 insertions(+), 66 deletions(-) diff --git a/cli/delete_test.go b/cli/delete_test.go index ba9004cb5828b..0a08ffe55f161 100644 --- a/cli/delete_test.go +++ b/cli/delete_test.go @@ -94,7 +94,7 @@ func TestDelete(t *testing.T) { // this way. ctx := testutil.Context(t, testutil.WaitShort) // nolint:gocritic // Unit test - err := api.Database.SoftDeleteUserByID(dbauthz.AsSystemRestricted(ctx), deleteMeUser.ID) + err := api.Database.UpdateUserDeletedByID(dbauthz.AsSystemRestricted(ctx), deleteMeUser.ID) require.NoError(t, err) inv, root := clitest.New(t, "delete", fmt.Sprintf("%s/%s", deleteMeUser.ID, workspace.Name), "-y", "--orphan") diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 1f98881eee278..8f64fe32b669c 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2546,9 +2546,9 @@ func (q *querier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) return q.db.RevokeDBCryptKey(ctx, activeKeyDigest) } -func (q *querier) SoftDeleteUserByID(ctx context.Context, id uuid.UUID) error { - return deleteQ(q.log, q.auth, q.db.GetUserByID, q.db.SoftDeleteUserByID)(ctx, id) -} +//func (q *querier) UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error { +// return deleteQ(q.log, q.auth, q.db.GetUserByID, q.db.UpdateUserDeletedByID)(ctx, id) +//} func (q *querier) TryAcquireLock(ctx context.Context, id int64) (bool, error) { return q.db.TryAcquireLock(ctx, id) @@ -2871,6 +2871,10 @@ func (q *querier) UpdateUserAppearanceSettings(ctx context.Context, arg database return q.db.UpdateUserAppearanceSettings(ctx, arg) } +func (q *querier) UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error { + panic("not implemented") +} + func (q *querier) UpdateUserHashedPassword(ctx context.Context, arg database.UpdateUserHashedPasswordParams) error { user, err := q.db.GetUserByID(ctx, arg.ID) if err != nil { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index ba5c50cc64b96..cadea8aad195a 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -1014,7 +1014,7 @@ func (s *MethodTestSuite) TestUser() { LoginType: database.LoginTypeOIDC, }).Asserts(u, rbac.ActionUpdate) })) - s.Run("SoftDeleteUserByID", s.Subtest(func(db database.Store, check *expects) { + s.Run("UpdateUserDeletedByID", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) check.Args(u.ID).Asserts(u, rbac.ActionDelete).Returns() })) diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 24fb18f99594b..4e014f73777f3 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -310,7 +310,7 @@ func User(t testing.TB, db database.Store, orig database.User) database.User { } if orig.Deleted { - err = db.SoftDeleteUserByID(genCtx, user.ID) + err = db.UpdateUserDeletedByID(genCtx, user.ID) require.NoError(t, err, "set user as deleted") } return user diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index ce7d496d6895d..55ed057da3f83 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -6138,28 +6138,6 @@ func (q *FakeQuerier) RevokeDBCryptKey(_ context.Context, activeKeyDigest string return sql.ErrNoRows } -func (q *FakeQuerier) SoftDeleteUserByID(_ context.Context, id uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, u := range q.users { - if u.ID == id { - u.Deleted = true - q.users[i] = u - // NOTE: In the real world, this is done by a trigger. - q.apiKeys = slices.DeleteFunc(q.apiKeys, func(u database.APIKey) bool { - return id == u.UserID - }) - - q.userLinks = slices.DeleteFunc(q.userLinks, func(u database.UserLink) bool { - return id == u.UserID - }) - return nil - } - } - return sql.ErrNoRows -} - func (*FakeQuerier) TryAcquireLock(_ context.Context, _ int64) (bool, error) { return false, xerrors.New("TryAcquireLock must only be called within a transaction") } @@ -6762,6 +6740,28 @@ func (q *FakeQuerier) UpdateUserAppearanceSettings(_ context.Context, arg databa return database.User{}, sql.ErrNoRows } +func (q *FakeQuerier) UpdateUserDeletedByID(_ context.Context, id uuid.UUID) error { + q.mutex.Lock() + defer q.mutex.Unlock() + + for i, u := range q.users { + if u.ID == id { + u.Deleted = true + q.users[i] = u + // NOTE: In the real world, this is done by a trigger. + q.apiKeys = slices.DeleteFunc(q.apiKeys, func(u database.APIKey) bool { + return id == u.UserID + }) + + q.userLinks = slices.DeleteFunc(q.userLinks, func(u database.UserLink) bool { + return id == u.UserID + }) + return nil + } + } + return sql.ErrNoRows +} + func (q *FakeQuerier) UpdateUserHashedPassword(_ context.Context, arg database.UpdateUserHashedPasswordParams) error { if err := validateDatabaseType(arg); err != nil { return err diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index a87fe7b8dc416..d0a8afca2ee2a 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -1642,13 +1642,6 @@ func (m metricsStore) RevokeDBCryptKey(ctx context.Context, activeKeyDigest stri return r0 } -func (m metricsStore) SoftDeleteUserByID(ctx context.Context, id uuid.UUID) error { - start := time.Now() - r0 := m.s.SoftDeleteUserByID(ctx, id) - m.queryLatencies.WithLabelValues("SoftDeleteUserByID").Observe(time.Since(start).Seconds()) - return r0 -} - func (m metricsStore) TryAcquireLock(ctx context.Context, pgTryAdvisoryXactLock int64) (bool, error) { start := time.Now() ok, err := m.s.TryAcquireLock(ctx, pgTryAdvisoryXactLock) @@ -1838,6 +1831,13 @@ func (m metricsStore) UpdateUserAppearanceSettings(ctx context.Context, arg data return r0, r1 } +func (m metricsStore) UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error { + start := time.Now() + r0 := m.s.UpdateUserDeletedByID(ctx, id) + m.queryLatencies.WithLabelValues("UpdateUserDeletedByID").Observe(time.Since(start).Seconds()) + return r0 +} + func (m metricsStore) UpdateUserHashedPassword(ctx context.Context, arg database.UpdateUserHashedPasswordParams) error { start := time.Now() err := m.s.UpdateUserHashedPassword(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index b59a0b38cac3d..9d4441daf4777 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -3469,20 +3469,6 @@ func (mr *MockStoreMockRecorder) RevokeDBCryptKey(arg0, arg1 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeDBCryptKey", reflect.TypeOf((*MockStore)(nil).RevokeDBCryptKey), arg0, arg1) } -// SoftDeleteUserByID mocks base method. -func (m *MockStore) SoftDeleteUserByID(arg0 context.Context, arg1 uuid.UUID) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SoftDeleteUserByID", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// SoftDeleteUserByID indicates an expected call of SoftDeleteUserByID. -func (mr *MockStoreMockRecorder) SoftDeleteUserByID(arg0, arg1 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SoftDeleteUserByID", reflect.TypeOf((*MockStore)(nil).SoftDeleteUserByID), arg0, arg1) -} - // TryAcquireLock mocks base method. func (m *MockStore) TryAcquireLock(arg0 context.Context, arg1 int64) (bool, error) { m.ctrl.T.Helper() @@ -3871,6 +3857,20 @@ func (mr *MockStoreMockRecorder) UpdateUserAppearanceSettings(arg0, arg1 any) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserAppearanceSettings", reflect.TypeOf((*MockStore)(nil).UpdateUserAppearanceSettings), arg0, arg1) } +// UpdateUserDeletedByID mocks base method. +func (m *MockStore) UpdateUserDeletedByID(arg0 context.Context, arg1 uuid.UUID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateUserDeletedByID", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateUserDeletedByID indicates an expected call of UpdateUserDeletedByID. +func (mr *MockStoreMockRecorder) UpdateUserDeletedByID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserDeletedByID", reflect.TypeOf((*MockStore)(nil).UpdateUserDeletedByID), arg0, arg1) +} + // UpdateUserHashedPassword mocks base method. func (m *MockStore) UpdateUserHashedPassword(arg0 context.Context, arg1 database.UpdateUserHashedPasswordParams) error { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index cf32a14ede7fb..2fce8b08555f4 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -322,7 +322,6 @@ type sqlcQuerier interface { ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgentPortShare, error) RegisterWorkspaceProxy(ctx context.Context, arg RegisterWorkspaceProxyParams) (WorkspaceProxy, error) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error - SoftDeleteUserByID(ctx context.Context, id uuid.UUID) error // Non blocking lock. Returns true if the lock was acquired, false otherwise. // // This must be called from within a transaction. The lock will be automatically @@ -355,6 +354,7 @@ type sqlcQuerier interface { UpdateTemplateVersionExternalAuthProvidersByJobID(ctx context.Context, arg UpdateTemplateVersionExternalAuthProvidersByJobIDParams) error UpdateTemplateWorkspacesLastUsedAt(ctx context.Context, arg UpdateTemplateWorkspacesLastUsedAtParams) error UpdateUserAppearanceSettings(ctx context.Context, arg UpdateUserAppearanceSettingsParams) (User, error) + UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error UpdateUserHashedPassword(ctx context.Context, arg UpdateUserHashedPasswordParams) error UpdateUserLastSeenAt(ctx context.Context, arg UpdateUserLastSeenAtParams) (User, error) UpdateUserLink(ctx context.Context, arg UpdateUserLinkParams) (UserLink, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 423870a6c0edb..b3465bac5eb21 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -7779,20 +7779,6 @@ func (q *sqlQuerier) InsertUser(ctx context.Context, arg InsertUserParams) (User return i, err } -const softDeleteUserByID = `-- name: SoftDeleteUserByID :exec -UPDATE - users -SET - deleted = true -WHERE - id = $1 -` - -func (q *sqlQuerier) SoftDeleteUserByID(ctx context.Context, id uuid.UUID) error { - _, err := q.db.ExecContext(ctx, softDeleteUserByID, id) - return err -} - const updateInactiveUsersToDormant = `-- name: UpdateInactiveUsersToDormant :many UPDATE users @@ -7879,6 +7865,20 @@ func (q *sqlQuerier) UpdateUserAppearanceSettings(ctx context.Context, arg Updat return i, err } +const updateUserDeletedByID = `-- name: UpdateUserDeletedByID :exec +UPDATE + users +SET + deleted = true +WHERE + id = $1 +` + +func (q *sqlQuerier) UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error { + _, err := q.db.ExecContext(ctx, updateUserDeletedByID, id) + return err +} + const updateUserHashedPassword = `-- name: UpdateUserHashedPassword :exec UPDATE users diff --git a/coderd/database/queries/users.sql b/coderd/database/queries/users.sql index 5341671586bc0..5062b14429427 100644 --- a/coderd/database/queries/users.sql +++ b/coderd/database/queries/users.sql @@ -112,7 +112,7 @@ SET WHERE id = $1; --- name: SoftDeleteUserByID :exec +-- name: UpdateUserDeletedByID :exec UPDATE users SET diff --git a/coderd/users.go b/coderd/users.go index d56fe749661ce..857b5ee0b69d4 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -521,7 +521,7 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) { return } - err = api.Database.SoftDeleteUserByID(ctx, user.ID) + err = api.Database.UpdateUserDeletedByID(ctx, user.ID) if dbauthz.IsNotAuthorizedError(err) { httpapi.Forbidden(rw) return From 149875ea75cdd0bc72992b0e0047b840e40632f5 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 20 Feb 2024 12:57:33 -0600 Subject: [PATCH 25/26] Fix dbauthz --- coderd/database/dbauthz/dbauthz.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 8f64fe32b669c..d52aa3126d337 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2546,10 +2546,6 @@ func (q *querier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) return q.db.RevokeDBCryptKey(ctx, activeKeyDigest) } -//func (q *querier) UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error { -// return deleteQ(q.log, q.auth, q.db.GetUserByID, q.db.UpdateUserDeletedByID)(ctx, id) -//} - func (q *querier) TryAcquireLock(ctx context.Context, id int64) (bool, error) { return q.db.TryAcquireLock(ctx, id) } @@ -2872,7 +2868,7 @@ func (q *querier) UpdateUserAppearanceSettings(ctx context.Context, arg database } func (q *querier) UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error { - panic("not implemented") + return deleteQ(q.log, q.auth, q.db.GetUserByID, q.db.UpdateUserDeletedByID)(ctx, id) } func (q *querier) UpdateUserHashedPassword(ctx context.Context, arg database.UpdateUserHashedPasswordParams) error { From f28491ee90a5e9b551c41a45e21a1527eecd5cf8 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 20 Feb 2024 12:59:07 -0600 Subject: [PATCH 26/26] bump migrations --- ...ink.down.sql => 000194_trigger_delete_user_user_link.down.sql} | 0 ...er_link.up.sql => 000194_trigger_delete_user_user_link.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000192_trigger_delete_user_user_link.down.sql => 000194_trigger_delete_user_user_link.down.sql} (100%) rename coderd/database/migrations/{000192_trigger_delete_user_user_link.up.sql => 000194_trigger_delete_user_user_link.up.sql} (100%) diff --git a/coderd/database/migrations/000192_trigger_delete_user_user_link.down.sql b/coderd/database/migrations/000194_trigger_delete_user_user_link.down.sql similarity index 100% rename from coderd/database/migrations/000192_trigger_delete_user_user_link.down.sql rename to coderd/database/migrations/000194_trigger_delete_user_user_link.down.sql diff --git a/coderd/database/migrations/000192_trigger_delete_user_user_link.up.sql b/coderd/database/migrations/000194_trigger_delete_user_user_link.up.sql similarity index 100% rename from coderd/database/migrations/000192_trigger_delete_user_user_link.up.sql rename to coderd/database/migrations/000194_trigger_delete_user_user_link.up.sql