From 55ce468492f5761078e698a4a1bc11613d7cce20 Mon Sep 17 00:00:00 2001 From: Chad Burt Date: Tue, 29 Oct 2024 11:16:46 -0700 Subject: [PATCH] Fix for un-intentional report cache busting when copying a collection --- packages/api/migrations/committed/000337.sql | 75 ++++ packages/api/migrations/current.sql | 2 +- packages/api/schema.sql | 363 +++---------------- packages/api/src/plugins/sketchingPlugin.ts | 11 +- 4 files changed, 143 insertions(+), 308 deletions(-) create mode 100644 packages/api/migrations/committed/000337.sql diff --git a/packages/api/migrations/committed/000337.sql b/packages/api/migrations/committed/000337.sql new file mode 100644 index 000000000..18d1acff6 --- /dev/null +++ b/packages/api/migrations/committed/000337.sql @@ -0,0 +1,75 @@ +--! Previous: sha1:b8cbc01e7d5166e77a68f5e3072082b77ff40769 +--! Hash: sha1:34bba5d9e6dcba7e4de57df697d33c04c760aa76 + +-- Enter migration here +CREATE OR REPLACE FUNCTION public.copy_sketch_toc_item_recursive(parent_id integer, type public.sketch_child_type, append_copy_to_name boolean) RETURNS integer + LANGUAGE plpgsql + security definer + AS $$ + declare + copy_id int; + child_copy_id int; + is_collection boolean; + child record; + begin + if type = 'sketch' then + if it_me((select user_id from sketches where id = parent_id)) = false then + raise exception 'Permission denied'; + end if; + else + if it_me((select user_id from sketch_folders where id = parent_id)) = false then + raise exception 'Permission denied'; + end if; + end if; + SET session_replication_role = replica; + if type = 'sketch' then + -- copy it and get the copy id + select id, is_collection(sketch_class_id) from copy_sketch(parent_id) into copy_id, is_collection; + -- When copying a sketch from the forum, make sure its folder_id and collection_id are cleared + if ((select shared_in_forum from sketches where id = parent_id)) then + update sketches set collection_id = null, folder_id = null where id = copy_id; + end if; + if append_copy_to_name = true then + update sketches set name = name || ' (copy)' where id = copy_id; + end if; + if is_collection then + -- copy subfolders and sub-sketches + FOR child IN SELECT * FROM get_children_of_collection(parent_id) + LOOP + raise notice 'copying %', child.type; + select copy_sketch_toc_item_recursive(child.id, child.type, false) into child_copy_id; + raise notice 'assigning collection_id=%', copy_id; + if child.type = 'sketch_folder' then + update sketch_folders set collection_id = copy_id, folder_id = null where id = child_copy_id; + else + update sketches set collection_id = copy_id, folder_id = null where id = child_copy_id; + end if; + END LOOP; + end if; + elsif type = 'sketch_folder' then + -- copy it and get the copy id + select id from copy_sketch_folder(parent_id) into copy_id; + -- When copying a sketch from the forum, make sure its folder_id and collection_id are cleared + if ((select shared_in_forum from sketch_folders where id = parent_id)) then + update sketch_folders set collection_id = null, folder_id = null where id = copy_id; + end if; + if append_copy_to_name = true then + update sketch_folders set name = name || ' (copy)' where id = copy_id; + end if; + -- copy subfolders and sub-sketches + FOR child IN SELECT * FROM get_children_of_folder(parent_id) + LOOP + raise notice 'copying %', child.type; + select copy_sketch_toc_item_recursive(child.id, child.type, false) into child_copy_id; + raise notice 'assigning folder_id=%', copy_id; + if child.type = 'sketch_folder' then + update sketch_folders set folder_id = copy_id, collection_id = null where id = child_copy_id; + else + update sketches set folder_id = copy_id, collection_id = null where id = child_copy_id; + end if; + END LOOP; + end if; + SET session_replication_role = default; + return copy_id; + end; + $$; diff --git a/packages/api/migrations/current.sql b/packages/api/migrations/current.sql index 8da533983..1e956058e 100644 --- a/packages/api/migrations/current.sql +++ b/packages/api/migrations/current.sql @@ -1 +1 @@ --- Enter migration here +-- Enter migration here \ No newline at end of file diff --git a/packages/api/schema.sql b/packages/api/schema.sql index 2c1a2b216..932c316bf 100644 --- a/packages/api/schema.sql +++ b/packages/api/schema.sql @@ -2920,12 +2920,12 @@ CREATE TABLE public.archived_data_sources ( sprite_ids integer[] GENERATED ALWAYS AS (public.extract_sprite_ids((mapbox_gl_style)::text)) STORED, changelog text, source_layer text, - project_id integer NOT NULL, bounds numeric[], created_at timestamp with time zone DEFAULT now(), sublayer text, sublayer_type public.sublayer_type, - dynamic_metadata boolean DEFAULT false NOT NULL + dynamic_metadata boolean DEFAULT false NOT NULL, + project_id integer NOT NULL ); @@ -5357,7 +5357,7 @@ CREATE FUNCTION public.copy_sketch_folder(folder_id integer) RETURNS public.sket -- CREATE FUNCTION public.copy_sketch_toc_item_recursive(parent_id integer, type public.sketch_child_type, append_copy_to_name boolean) RETURNS integer - LANGUAGE plpgsql + LANGUAGE plpgsql SECURITY DEFINER AS $$ declare copy_id int; @@ -5365,6 +5365,16 @@ CREATE FUNCTION public.copy_sketch_toc_item_recursive(parent_id integer, type pu is_collection boolean; child record; begin + if type = 'sketch' then + if it_me((select user_id from sketches where id = parent_id)) = false then + raise exception 'Permission denied'; + end if; + else + if it_me((select user_id from sketch_folders where id = parent_id)) = false then + raise exception 'Permission denied'; + end if; + end if; + SET session_replication_role = replica; if type = 'sketch' then -- copy it and get the copy id select id, is_collection(sketch_class_id) from copy_sketch(parent_id) into copy_id, is_collection; @@ -5412,6 +5422,7 @@ CREATE FUNCTION public.copy_sketch_toc_item_recursive(parent_id integer, type pu end if; END LOOP; end if; + SET session_replication_role = default; return copy_id; end; $$; @@ -6460,7 +6471,6 @@ CREATE TABLE public.table_of_contents_items ( translated_props jsonb DEFAULT '{}'::jsonb NOT NULL, fts_simple tsvector GENERATED ALWAYS AS (public.toc_to_tsvector('simple'::text, title, metadata, translated_props)) STORED, fts_en tsvector GENERATED ALWAYS AS (public.toc_to_tsvector('english'::text, title, metadata, translated_props)) STORED, - fts_es tsvector GENERATED ALWAYS AS (public.toc_to_tsvector('spanish'::text, title, metadata, translated_props)) STORED, fts_pt tsvector GENERATED ALWAYS AS (public.toc_to_tsvector('portuguese'::text, title, metadata, translated_props)) STORED, fts_ar tsvector GENERATED ALWAYS AS (public.toc_to_tsvector('arabic'::text, title, metadata, translated_props)) STORED, fts_da tsvector GENERATED ALWAYS AS (public.toc_to_tsvector('danish'::text, title, metadata, translated_props)) STORED, @@ -6474,6 +6484,7 @@ CREATE TABLE public.table_of_contents_items ( fts_ro tsvector GENERATED ALWAYS AS (public.toc_to_tsvector('romanian'::text, title, metadata, translated_props)) STORED, fts_ru tsvector GENERATED ALWAYS AS (public.toc_to_tsvector('russian'::text, title, metadata, translated_props)) STORED, fts_sv tsvector GENERATED ALWAYS AS (public.toc_to_tsvector('swedish'::text, title, metadata, translated_props)) STORED, + fts_es tsvector GENERATED ALWAYS AS (public.toc_to_tsvector('spanish'::text, title, metadata, translated_props)) STORED, data_source_type text, original_source_upload_available boolean DEFAULT false NOT NULL, CONSTRAINT table_of_contents_items_metadata_check CHECK (((metadata IS NULL) OR (char_length((metadata)::text) < 100000))), @@ -9657,7 +9668,7 @@ COMMENT ON FUNCTION public.get_sprite_data_for_screenshot(bookmark public.map_bo CREATE FUNCTION public.get_supported_languages() RETURNS jsonb LANGUAGE sql IMMUTABLE AS $$ - select '{"simple": "simple", "english": "en", "spanish": "es", "portuguese": "pt", "arabic": "ar", "danish": "da", "dutch": "nl", "french": "fr", "german": "de", "greek": "el", "indonesian": "id", "italian": "it", "lithuanian": "lt", "norwegian": "no", "romanian": "ro", "russian": "ru", "swedish": "sv"}'::jsonb; + select '{"simple": "simple", "english": "EN", "spanish": "es", "portuguese": "pt", "arabic": "ar", "danish": "da", "dutch": "nl", "french": "fr", "german": "de", "indonesian": "id", "italian": "it", "lithuanian": "lt", "norwegian": "no", "romanian": "ro", "russian": "ru", "swedish": "sv"}'::jsonb; $$; @@ -9732,6 +9743,23 @@ CREATE FUNCTION public.has_session() RETURNS boolean COMMENT ON FUNCTION public.has_session() IS '@omit'; +-- +-- Name: id_lookup_get_key(integer); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.id_lookup_get_key(key integer) RETURNS integer + LANGUAGE plpgsql + AS $$ + begin + if lookup is null then + raise exception 'lookup is null'; + else + return (lookup->key)::int; + end if; + end; + $$; + + -- -- Name: id_lookup_get_key(jsonb, integer); Type: FUNCTION; Schema: public; Owner: - -- @@ -13981,309 +14009,25 @@ CREATE FUNCTION public.search_overlays(lang text, query text, "projectId" intege LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ declare - supported_languages jsonb := get_supported_languages(); - config regconfig; - q tsquery := websearch_to_tsquery(config, query); + q tsquery := websearch_to_tsquery('english'::regconfig, query); begin - select key::regconfig into config from jsonb_each_text(get_supported_languages()) where value = lower(lang); IF position(' ' in query) <= 0 THEN - q := to_tsquery(config, query || ':*'); - end if; - if config is null then - q = plainto_tsquery('simple', query); - IF position(' ' in query) <= 0 THEN - q := to_tsquery('simple', query || ':*'); - end if; - -- use the simple index - return query select - id, - stable_id, - ts_headline('simple', title, q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline('simple', jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_simple @@ q - limit - coalesce("limit", 10); - elsif config = 'english'::regconfig then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_en @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'es' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_es @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'pt' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline('portuguese', jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_pt @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'ar' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline('arabic', jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_ar @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'da' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_da @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'nl' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_nl @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'fr' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_fr @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'de' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_de @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'el' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_el @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'id' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_id @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'it' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_it @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'lt' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_lt @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'no' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_no @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'ro' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_ro @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'ru' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_ru @@ q - limit - coalesce("limit", 10); - elsif lower(lang) = 'sv' then - return query select - id, - stable_id, - ts_headline(config, coalesce( - translated_props->lang->>'title'::text, title - ), q, 'StartSel=<<<, StopSel=>>>') as title_headline, - ts_headline(config, jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, - is_folder - from - table_of_contents_items - where - project_id = "projectId" and - is_draft = draft and - fts_sv @@ q - limit - coalesce("limit", 10); - else - raise exception 'Unsupported language: %', lang; + q := to_tsquery('english'::regconfig, query || ':*'); end if; + return query select + id, + stable_id, + ts_headline('english', title, q, 'StartSel=<<<, StopSel=>>>') as title_headline, + ts_headline('english', jsonb_array_to_string(collect_prosemirror_text_nodes(metadata)), q, 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<<, StopSel=>>>') as metadata_headline, + is_folder + from + table_of_contents_items + where + project_id = "projectId" and + is_draft = draft and + fts_en @@ q + limit + coalesce("limit", 10); end; $$; @@ -23981,8 +23725,8 @@ GRANT UPDATE(hide_overlays) ON TABLE public.projects TO seasketch_user; -- Name: COLUMN projects.enable_download_by_default; Type: ACL; Schema: public; Owner: - -- -GRANT SELECT(enable_download_by_default),UPDATE(enable_download_by_default) ON TABLE public.projects TO seasketch_user; GRANT SELECT(enable_download_by_default) ON TABLE public.projects TO anon; +GRANT UPDATE(enable_download_by_default) ON TABLE public.projects TO seasketch_user; -- @@ -27624,6 +27368,13 @@ REVOKE ALL ON FUNCTION public.hmac(bytea, bytea, text) FROM PUBLIC; REVOKE ALL ON FUNCTION public.hmac(text, text, text) FROM PUBLIC; +-- +-- Name: FUNCTION id_lookup_get_key(key integer); Type: ACL; Schema: public; Owner: - +-- + +REVOKE ALL ON FUNCTION public.id_lookup_get_key(key integer) FROM PUBLIC; + + -- -- Name: FUNCTION id_lookup_get_key(lookup jsonb, key integer); Type: ACL; Schema: public; Owner: - -- diff --git a/packages/api/src/plugins/sketchingPlugin.ts b/packages/api/src/plugins/sketchingPlugin.ts index b9f2e4ee2..32783f649 100644 --- a/packages/api/src/plugins/sketchingPlugin.ts +++ b/packages/api/src/plugins/sketchingPlugin.ts @@ -477,7 +477,16 @@ const SketchingPlugin = makeExtendSchemaPlugin((build) => { resolveInfo ) => { const { pgClient } = context; - + if (!forForum) { + // check that the user owns this sketch + const { rows } = await pgClient.query( + `select user_id from sketches where id = $1`, + [id] + ); + if (rows.length === 0) { + throw new Error("Sketch not found or permission denied"); + } + } const { rows: [row], } = await pgClient.query(