diff --git a/SQL Scripts/functions/anonymize_profile.sql b/SQL Scripts/functions/anonymize_profile.sql new file mode 100644 index 0000000..9721bcb --- /dev/null +++ b/SQL Scripts/functions/anonymize_profile.sql @@ -0,0 +1,15 @@ +CREATE + OR REPLACE FUNCTION public.anonymize_profile() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER +AS +$$ +BEGIN + UPDATE public.profiles + SET first_name = '', last_name = '', nickname = '', email = '', avatar_url = '' + WHERE id = OLD.id; + RETURN new; +END; +$$ +; diff --git a/SQL Scripts/functions/change_org_group_membership.sql b/SQL Scripts/functions/change_org_group_membership.sql new file mode 100644 index 0000000..26703a2 --- /dev/null +++ b/SQL Scripts/functions/change_org_group_membership.sql @@ -0,0 +1,12 @@ +CREATE OR REPLACE FUNCTION change_org_group_membership(_user_id uuid, _new_group_id uuid) RETURNS BOOLEAN +AS $body$ + BEGIN + + IF public.is_admin_organization(auth.uid()) THEN + UPDATE public.group_users SET type_id = _new_group_id WHERE user_id = _user_id AND group_type = 'organization'; + RETURN TRUE; + END IF; + + RETURN FALSE; +END; +$body$ LANGUAGE plpgsql SECURITY DEFINER; diff --git a/SQL Scripts/functions/delete_user.sql b/SQL Scripts/functions/delete_user.sql new file mode 100644 index 0000000..c3a8f75 --- /dev/null +++ b/SQL Scripts/functions/delete_user.sql @@ -0,0 +1,11 @@ +CREATE OR REPLACE FUNCTION delete_user(_user_id uuid) RETURNS BOOLEAN AS $$ +BEGIN + IF is_admin_organization(auth.uid()) THEN + DELETE FROM auth.users WHERE auth.users.id = _user_id; + UPDATE public.profiles + SET first_name = '', last_name = '', nickname = '', email = '', avatar_url = '' + WHERE id = _user_id; + RETURN TRUE; + END IF; + RETURN FALSE; +END $$ LANGUAGE 'plpgsql' SECURITY DEFINER; diff --git a/SQL Scripts/functions/get_profiles_extended.sql b/SQL Scripts/functions/get_profiles_extended.sql new file mode 100644 index 0000000..f3e7d76 --- /dev/null +++ b/SQL Scripts/functions/get_profiles_extended.sql @@ -0,0 +1,31 @@ +CREATE OR REPLACE FUNCTION get_profiles_extended() RETURNS TABLE ( id uuid, + nickname VARCHAR, + first_name VARCHAR, + last_name VARCHAR, + avatar_url VARCHAR, + email_address VARCHAR, + last_sign_in_at timestamptz, + org_group_id uuid, + org_group_name VARCHAR ) +AS $body$ + BEGIN + + IF public.is_admin_organization(auth.uid()) THEN + RETURN QUERY + SELECT p.id, + p.nickname, + p.first_name, + p.last_name, + p.avatar_url, + u.email, + u.last_sign_in_at, + og.id, + og.name + FROM public.profiles p + INNER JOIN public.group_users gu ON p.id = gu.user_id + AND gu.group_type = 'organization' + INNER JOIN public.organization_groups og ON og.id = gu.type_id + INNER JOIN auth.users u ON u.id = p.id; + END IF; +END; +$body$ LANGUAGE plpgsql SECURITY DEFINER; diff --git a/SQL Scripts/policies/installed_plugins.sql b/SQL Scripts/policies/installed_plugins.sql new file mode 100644 index 0000000..0c9b018 --- /dev/null +++ b/SQL Scripts/policies/installed_plugins.sql @@ -0,0 +1,29 @@ +DROP POLICY IF EXISTS "Users with correct policies can SELECT on installed_plugins" ON public.installed_plugins; + +CREATE POLICY "Users with correct policies can SELECT on installed_plugins" ON public.installed_plugins FOR SELECT TO authenticated + USING ( + (public.check_action_policy_organization(auth.uid(), 'installed_plugins', 'SELECT') OR + public.check_action_policy_project(auth.uid(), 'installed_plugins', 'SELECT', project_id)) + ); + +DROP POLICY IF EXISTS "Users with correct policies can INSERT on installed_plugins" ON public.installed_plugins; + +CREATE POLICY "Users with correct policies can INSERT on installed_plugins" ON public.installed_plugins FOR INSERT TO authenticated + WITH CHECK (public.check_action_policy_organization(auth.uid(), 'installed_plugins', 'INSERT') OR + public.check_action_policy_project(auth.uid(), 'installed_plugins', 'INSERT', project_id)); + +DROP POLICY IF EXISTS "Users with correct policies can UPDATE on installed_plugins" ON public.installed_plugins; + +CREATE POLICY "Users with correct policies can UPDATE on installed_plugins" ON public.installed_plugins FOR UPDATE TO authenticated + USING ( + public.check_action_policy_organization(auth.uid(), 'installed_plugins', 'UPDATE') OR + public.check_action_policy_project(auth.uid(), 'installed_plugins', 'UPDATE', project_id) + ) + WITH CHECK (public.check_action_policy_organization(auth.uid(), 'installed_plugins', 'UPDATE') OR + public.check_action_policy_project(auth.uid(), 'installed_plugins', 'UPDATE', project_id)); + +DROP POLICY IF EXISTS "Users with correct policies can DELETE on installed_plugins" ON public.installed_plugins; + +CREATE POLICY "Users with correct policies can DELETE on installed_plugins" ON public.installed_plugins FOR DELETE TO authenticated + USING (public.check_action_policy_organization(auth.uid(), 'installed_plugins', 'DELETE') OR + public.check_action_policy_project(auth.uid(), 'installed_plugins', 'DELETE', project_id)); diff --git a/SQL Scripts/tables/installed_plugins.sql b/SQL Scripts/tables/installed_plugins.sql new file mode 100644 index 0000000..0622790 --- /dev/null +++ b/SQL Scripts/tables/installed_plugins.sql @@ -0,0 +1,13 @@ +CREATE TABLE installed_plugins +( + id uuid NOT NULL DEFAULT uuid_generate_v4() PRIMARY KEY, + created_at timestamp WITH TIME ZONE DEFAULT NOW(), + created_by uuid REFERENCES public.profiles, + updated_at timestamptz, + updated_by uuid REFERENCES public.profiles, + project_id uuid REFERENCES public.projects NOT NULL, + plugin_name VARCHAR NOT NULL, + plugin_id uuid NOT NULL, + plugin_settings json +) + diff --git a/SQL Scripts/triggers/installed_plugins/on_installed_plugin_created.sql b/SQL Scripts/triggers/installed_plugins/on_installed_plugin_created.sql new file mode 100644 index 0000000..3f9815b --- /dev/null +++ b/SQL Scripts/triggers/installed_plugins/on_installed_plugin_created.sql @@ -0,0 +1,5 @@ +DROP TRIGGER IF EXISTS on_installed_plugin_updated + ON public.installed_plugins; +CREATE TRIGGER on_installed_plugin_updated + BEFORE INSERT ON public.installed_plugins + FOR EACH ROW EXECUTE PROCEDURE create_dates_and_user(); \ No newline at end of file diff --git a/SQL Scripts/triggers/installed_plugins/on_installed_plugin_updated.sql b/SQL Scripts/triggers/installed_plugins/on_installed_plugin_updated.sql new file mode 100644 index 0000000..d645eb1 --- /dev/null +++ b/SQL Scripts/triggers/installed_plugins/on_installed_plugin_updated.sql @@ -0,0 +1,5 @@ +DROP TRIGGER IF EXISTS on_installed_plugin_updated + ON public.installed_plugins; +CREATE TRIGGER on_installed_plugin_updated + BEFORE UPDATE ON public.installed_plugins + FOR EACH ROW EXECUTE PROCEDURE update_dates_and_user(); \ No newline at end of file diff --git a/SQL Scripts/triggers/users/on_auth_user_deleted.sql b/SQL Scripts/triggers/users/on_auth_user_deleted.sql new file mode 100644 index 0000000..e0c6eb2 --- /dev/null +++ b/SQL Scripts/triggers/users/on_auth_user_deleted.sql @@ -0,0 +1,3 @@ +DROP TRIGGER IF EXISTS on_auth_user_deleted ON auth.users; + +CREATE TRIGGER on_auth_user_deleted AFTER DELETE ON auth.users FOR EACH ROW EXECUTE FUNCTION anonymize_profile (); \ No newline at end of file diff --git a/config.json b/config.json index f0f468f..a098c19 100644 --- a/config.json +++ b/config.json @@ -42,6 +42,25 @@ { "id": "6a4fec4c-a1c3-4d20-8451-c6ecba886a82", "table_name": "context_users", + "operation": "DELETE" + }, + "id": "79cd967d-f268-4bb8-9e84-0eafeac3307f", + "table_name": "installed_plugins", + "operation": "SELECT" + }, + { + "id": "d651e790-2dc2-4522-b876-9f27af71c5f6", + "table_name": "installed_plugins", + "operation": "INSERT" + }, + { + "id": "0b7820da-aceb-442e-9a5d-3fb3fcaa5254", + "table_name": "installed_plugins", + "operation": "UPDATE" + }, + { + "id": "b92a5f03-ac77-4f0e-907a-873c9d2f78bf", + "table_name": "installed_plugins "operation": "DELETE" }, { @@ -587,6 +606,10 @@ "3aa4d2bf-2127-4c66-8858-e9a6b59dbd07", "0377daa4-38b3-459d-8715-999532af1cb1", "6a4fec4c-a1c3-4d20-8451-c6ecba886a82" + "79cd967d-f268-4bb8-9e84-0eafeac3307f", + "d651e790-2dc2-4522-b876-9f27af71c5f6", + "0b7820da-aceb-442e-9a5d-3fb3fcaa5254", + "b92a5f03-ac77-4f0e-907a-873c9d2f78bf" ] }, { @@ -677,6 +700,10 @@ "51eb3610-a7ee-4fd6-9a71-65214aee0dd7", "3aa4d2bf-2127-4c66-8858-e9a6b59dbd07", "0377daa4-38b3-459d-8715-999532af1cb1" + "79cd967d-f268-4bb8-9e84-0eafeac3307f", + "d651e790-2dc2-4522-b876-9f27af71c5f6", + "0b7820da-aceb-442e-9a5d-3fb3fcaa5254", + "b92a5f03-ac77-4f0e-907a-873c9d2f78bf" ] }, { @@ -717,6 +744,7 @@ "0377daa4-38b3-459d-8715-999532af1cb1", "3aa4d2bf-2127-4c66-8858-e9a6b59dbd07", "51eb3610-a7ee-4fd6-9a71-65214aee0dd7" + "6ec09042-5dc0-4593-b506-d4c57c3e14cd" ] }, { @@ -734,6 +762,7 @@ "b716be7a-81b6-4d0a-a55c-a7ca60352ef3", "a4b82076-cf7d-4f7a-b24d-f12587d71590", "51eb3610-a7ee-4fd6-9a71-65214aee0dd7" + "79cd967d-f268-4bb8-9e84-0eafeac3307f" ] }, { @@ -762,6 +791,7 @@ "a4b82076-cf7d-4f7a-b24d-f12587d71590", "51eb3610-a7ee-4fd6-9a71-65214aee0dd7", "b716be7a-81b6-4d0a-a55c-a7ca60352ef3" + "79cd967d-f268-4bb8-9e84-0eafeac3307f" ] } ], diff --git a/supabase/migrations/20240129213542_open_edit_and_join.sql b/supabase/migrations/20240129213542_open_edit_and_join.sql index a1a3c38..d982bff 100644 --- a/supabase/migrations/20240129213542_open_edit_and_join.sql +++ b/supabase/migrations/20240129213542_open_edit_and_join.sql @@ -2,11 +2,11 @@ drop policy "Users with correct policies can SELECT on project_groups" on "publi drop policy "Users with correct policies can SELECT on projects" on "public"."projects"; -alter table "public"."contexts" add column "is_project_default" boolean default false; +alter table "public"."contexts" add column IF NOT EXISTS "is_project_default" boolean default false; -alter table "public"."projects" add column "is_open_edit" boolean default false; +alter table "public"."projects" add column IF NOT EXISTS "is_open_edit" boolean default false; -alter table "public"."projects" add column "is_open_join" boolean default false; +alter table "public"."projects" add column IF NOT EXISTS "is_open_join" boolean default false; set check_function_bodies = off; @@ -167,8 +167,8 @@ to authenticated using ((((is_archived IS FALSE) AND (is_open_join IS TRUE)) OR ((is_archived IS FALSE) AND (check_action_policy_organization(auth.uid(), 'projects'::character varying, 'SELECT'::operation_types) OR check_action_policy_project(auth.uid(), 'projects'::character varying, 'SELECT'::operation_types, id))))); -CREATE TRIGGER on_group_user_created_open_edit_check AFTER INSERT ON public.group_users FOR EACH ROW EXECUTE FUNCTION check_group_user_for_open_edit(); +CREATE OR REPLACE TRIGGER on_group_user_created_open_edit_check AFTER INSERT ON public.group_users FOR EACH ROW EXECUTE FUNCTION check_group_user_for_open_edit(); -CREATE TRIGGER on_layer_context_created_check_open_edit AFTER INSERT ON public.layer_contexts FOR EACH ROW EXECUTE FUNCTION check_layer_context_for_open_edit(); +CREATE OR REPLACE TRIGGER on_layer_context_created_check_open_edit AFTER INSERT ON public.layer_contexts FOR EACH ROW EXECUTE FUNCTION check_layer_context_for_open_edit(); diff --git a/supabase/migrations/20240208203416_fix_open_edit_issue.sql b/supabase/migrations/20240208203416_fix_open_edit_issue.sql index 7293561..db001b9 100644 --- a/supabase/migrations/20240208203416_fix_open_edit_issue.sql +++ b/supabase/migrations/20240208203416_fix_open_edit_issue.sql @@ -49,6 +49,6 @@ END $function$ ; -CREATE TRIGGER on_project_updated_check_open_edit AFTER UPDATE ON public.projects FOR EACH ROW EXECUTE FUNCTION check_for_project_open_edit_change(); +CREATE OR REPLACE TRIGGER on_project_updated_check_open_edit AFTER UPDATE ON public.projects FOR EACH ROW EXECUTE FUNCTION check_for_project_open_edit_change(); diff --git a/supabase/migrations/20240212134500_user_management_support.sql b/supabase/migrations/20240212134500_user_management_support.sql new file mode 100644 index 0000000..55a90bb --- /dev/null +++ b/supabase/migrations/20240212134500_user_management_support.sql @@ -0,0 +1,79 @@ +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.anonymize_profile() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ +BEGIN + UPDATE public.profiles + SET first_name = '', last_name = '', nickname = '', email = '', avatar_url = '' + WHERE id = OLD.id; + RETURN new; +END; +$function$ +; + +CREATE OR REPLACE FUNCTION public.change_org_group_membership(_user_id uuid, _new_group_id uuid) + RETURNS boolean + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ + BEGIN + + IF public.is_admin_organization(auth.uid()) THEN + UPDATE public.group_users SET type_id = _new_group_id WHERE user_id = _user_id AND group_type = 'organization'; + RETURN TRUE; + END IF; + + RETURN FALSE; +END; +$function$ +; + +CREATE OR REPLACE FUNCTION public.delete_user(_user_id uuid) + RETURNS boolean + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ +BEGIN + IF is_admin_organization(auth.uid()) THEN + DELETE FROM auth.users WHERE auth.users.id = _user_id; + UPDATE public.profiles + SET first_name = '', last_name = '', nickname = '', email = '', avatar_url = '' + WHERE id = _user_id; + RETURN TRUE; + END IF; + RETURN FALSE; +END $function$ +; + +CREATE OR REPLACE FUNCTION public.get_profiles_extended() + RETURNS TABLE(id uuid, nickname character varying, first_name character varying, last_name character varying, avatar_url character varying, email_address character varying, last_sign_in_at timestamp with time zone, org_group_id uuid, org_group_name character varying) + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ + BEGIN + + IF public.is_admin_organization(auth.uid()) THEN + RETURN QUERY + SELECT p.id, + p.nickname, + p.first_name, + p.last_name, + p.avatar_url, + u.email, + u.last_sign_in_at, + og.id, + og.name + FROM public.profiles p + INNER JOIN public.group_users gu ON p.id = gu.user_id + AND gu.group_type = 'organization' + INNER JOIN public.organization_groups og ON og.id = gu.type_id + INNER JOIN auth.users u ON u.id = p.id; + END IF; +END; +$function$ +; + + diff --git a/supabase/migrations/20240313184251_plugin-support.sql b/supabase/migrations/20240313184251_plugin-support.sql new file mode 100644 index 0000000..c663a98 --- /dev/null +++ b/supabase/migrations/20240313184251_plugin-support.sql @@ -0,0 +1,107 @@ +create table "public"."installed_plugins" ( + "id" uuid not null default uuid_generate_v4(), + "created_at" timestamp with time zone default now(), + "created_by" uuid, + "updated_at" timestamp with time zone, + "updated_by" uuid, + "project_id" uuid not null, + "plugin_name" character varying not null, + "plugin_id" uuid not null, + "plugin_settings" json +); + + +alter table "public"."installed_plugins" enable row level security; + +CREATE UNIQUE INDEX installed_plugins_pkey ON public.installed_plugins USING btree (id); + +alter table "public"."installed_plugins" add constraint "installed_plugins_pkey" PRIMARY KEY using index "installed_plugins_pkey"; + +alter table "public"."installed_plugins" add constraint "installed_plugins_created_by_fkey" FOREIGN KEY (created_by) REFERENCES profiles(id) not valid; + +alter table "public"."installed_plugins" validate constraint "installed_plugins_created_by_fkey"; + +alter table "public"."installed_plugins" add constraint "installed_plugins_project_id_fkey" FOREIGN KEY (project_id) REFERENCES projects(id) not valid; + +alter table "public"."installed_plugins" validate constraint "installed_plugins_project_id_fkey"; + +alter table "public"."installed_plugins" add constraint "installed_plugins_updated_by_fkey" FOREIGN KEY (updated_by) REFERENCES profiles(id) not valid; + +alter table "public"."installed_plugins" validate constraint "installed_plugins_updated_by_fkey"; + +grant delete on table "public"."installed_plugins" to "anon"; + +grant insert on table "public"."installed_plugins" to "anon"; + +grant references on table "public"."installed_plugins" to "anon"; + +grant select on table "public"."installed_plugins" to "anon"; + +grant trigger on table "public"."installed_plugins" to "anon"; + +grant truncate on table "public"."installed_plugins" to "anon"; + +grant update on table "public"."installed_plugins" to "anon"; + +grant delete on table "public"."installed_plugins" to "authenticated"; + +grant insert on table "public"."installed_plugins" to "authenticated"; + +grant references on table "public"."installed_plugins" to "authenticated"; + +grant select on table "public"."installed_plugins" to "authenticated"; + +grant trigger on table "public"."installed_plugins" to "authenticated"; + +grant truncate on table "public"."installed_plugins" to "authenticated"; + +grant update on table "public"."installed_plugins" to "authenticated"; + +grant delete on table "public"."installed_plugins" to "service_role"; + +grant insert on table "public"."installed_plugins" to "service_role"; + +grant references on table "public"."installed_plugins" to "service_role"; + +grant select on table "public"."installed_plugins" to "service_role"; + +grant trigger on table "public"."installed_plugins" to "service_role"; + +grant truncate on table "public"."installed_plugins" to "service_role"; + +grant update on table "public"."installed_plugins" to "service_role"; + +create policy "Users with correct policies can DELETE on installed_plugins" +on "public"."installed_plugins" +as permissive +for delete +to authenticated +using ((check_action_policy_organization(auth.uid(), 'installed_plugins'::character varying, 'DELETE'::operation_types) OR check_action_policy_project(auth.uid(), 'installed_plugins'::character varying, 'DELETE'::operation_types, project_id))); + + +create policy "Users with correct policies can INSERT on installed_plugins" +on "public"."installed_plugins" +as permissive +for insert +to authenticated +with check ((check_action_policy_organization(auth.uid(), 'installed_plugins'::character varying, 'INSERT'::operation_types) OR check_action_policy_project(auth.uid(), 'installed_plugins'::character varying, 'INSERT'::operation_types, project_id))); + + +create policy "Users with correct policies can SELECT on installed_plugins" +on "public"."installed_plugins" +as permissive +for select +to authenticated +using ((check_action_policy_organization(auth.uid(), 'installed_plugins'::character varying, 'SELECT'::operation_types) OR check_action_policy_project(auth.uid(), 'installed_plugins'::character varying, 'SELECT'::operation_types, project_id))); + + +create policy "Users with correct policies can UPDATE on installed_plugins" +on "public"."installed_plugins" +as permissive +for update +to authenticated +using ((check_action_policy_organization(auth.uid(), 'installed_plugins'::character varying, 'UPDATE'::operation_types) OR check_action_policy_project(auth.uid(), 'installed_plugins'::character varying, 'UPDATE'::operation_types, project_id))) +with check ((check_action_policy_organization(auth.uid(), 'installed_plugins'::character varying, 'UPDATE'::operation_types) OR check_action_policy_project(auth.uid(), 'installed_plugins'::character varying, 'UPDATE'::operation_types, project_id))); + + + diff --git a/supabase/migrations/20240313185748_plugin-support-2.sql b/supabase/migrations/20240313185748_plugin-support-2.sql new file mode 100644 index 0000000..7403709 --- /dev/null +++ b/supabase/migrations/20240313185748_plugin-support-2.sql @@ -0,0 +1,3 @@ +CREATE TRIGGER on_installed_plugin_updated BEFORE UPDATE ON public.installed_plugins FOR EACH ROW EXECUTE FUNCTION update_dates_and_user(); + + diff --git a/supabase/seed.sql b/supabase/seed.sql index 751a9c2..ca8d52c 100644 --- a/supabase/seed.sql +++ b/supabase/seed.sql @@ -101,7 +101,11 @@ VALUES ('51eb3610-a7ee-4fd6-9a71-65214aee0dd7', 'context_users', 'SELECT'), ('3aa4d2bf-2127-4c66-8858-e9a6b59dbd07', 'context_users', 'INSERT'), ('0377daa4-38b3-459d-8715-999532af1cb1', 'context_users', 'UPDATE'), - ('6a4fec4c-a1c3-4d20-8451-c6ecba886a82', 'context_users', 'DELETE'); + ('6a4fec4c-a1c3-4d20-8451-c6ecba886a82', 'context_users', 'DELETE'), + ('79cd967d-f268-4bb8-9e84-0eafeac3307f', 'installed_plugins', 'SELECT'), + ('d651e790-2dc2-4522-b876-9f27af71c5f6', 'installed_plugins', 'INSERT'), + ('0b7820da-aceb-442e-9a5d-3fb3fcaa5254', 'installed_plugins', 'UPDATE'), + ('b92a5f03-ac77-4f0e-907a-873c9d2f78bf', 'installed_plugins', 'DELETE'); ALTER TABLE public.role_policies ADD CONSTRAINT role_policies_policy_id_fkey FOREIGN KEY (policy_id) REFERENCES public.policies (id);