diff --git a/db/post_migrate/20230706080234_add_trigger_on_organizations.rb b/db/post_migrate/20230706080234_add_trigger_on_organizations.rb new file mode 100644 index 0000000000000000000000000000000000000000..e21a5e0eb23b4e5e8c89c846c807446c11c59095 --- /dev/null +++ b/db/post_migrate/20230706080234_add_trigger_on_organizations.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class AddTriggerOnOrganizations < Gitlab::Database::Migration[2.1] + include Gitlab::Database::SchemaHelpers + + TABLE_NAME = 'organizations' + FUNCTION_NAME = 'prevent_delete_of_default_organization' + TRIGGER_NAME = 'prevent_delete_of_default_organization_before_destroy' + + def up + default_org_id = Organizations::Organization::DEFAULT_ORGANIZATION_ID + + create_trigger_function(FUNCTION_NAME) do + <<~SQL + IF OLD.id = #{default_org_id} THEN + RAISE EXCEPTION 'Deletion of the default Organization is not allowed.'; + END IF; + RETURN OLD; + SQL + end + + create_trigger(TABLE_NAME, TRIGGER_NAME, FUNCTION_NAME, fires: 'BEFORE DELETE') + end + + def down + drop_trigger(TABLE_NAME, TRIGGER_NAME) + drop_function(FUNCTION_NAME) + end +end diff --git a/db/schema_migrations/20230706080234 b/db/schema_migrations/20230706080234 new file mode 100644 index 0000000000000000000000000000000000000000..a188efbf3ca176a70085324a1666c76b640cf96c --- /dev/null +++ b/db/schema_migrations/20230706080234 @@ -0,0 +1 @@ +a76b57ff9a9e62c1e8d73c46a9bdf6512f0fe48d3b95b4c9a291d602d54d7a33 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index f50f65fbd6b8aa58d0e9605493b52ed85e027773..47bf58b83016b4c3ece05aa5cac0aad7e9bba9a5 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -191,6 +191,18 @@ CREATE FUNCTION postgres_pg_stat_activity_autovacuum() RETURNS TABLE(query text, AND backend_type = 'autovacuum worker' $$; +CREATE FUNCTION prevent_delete_of_default_organization() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN +IF OLD.id = 1 THEN + RAISE EXCEPTION 'Deletion of the default Organization is not allowed.'; +END IF; +RETURN OLD; + +END +$$; + CREATE FUNCTION set_has_external_issue_tracker() RETURNS trigger LANGUAGE plpgsql AS $$ @@ -35228,6 +35240,8 @@ CREATE TRIGGER organizations_loose_fk_trigger AFTER DELETE ON organizations REFE CREATE TRIGGER p_ci_builds_loose_fk_trigger AFTER DELETE ON p_ci_builds REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); +CREATE TRIGGER prevent_delete_of_default_organization_before_destroy BEFORE DELETE ON organizations FOR EACH ROW EXECUTE FUNCTION prevent_delete_of_default_organization(); + CREATE TRIGGER projects_loose_fk_trigger AFTER DELETE ON projects REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); CREATE TRIGGER push_rules_loose_fk_trigger AFTER DELETE ON push_rules REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); diff --git a/spec/models/organizations/organization_spec.rb b/spec/models/organizations/organization_spec.rb index f462fbefcccec523d87abaa66ac57911d4130494..a9cac30e9a173a44f53dfe0e7bed5a37bc419828 100644 --- a/spec/models/organizations/organization_spec.rb +++ b/spec/models/organizations/organization_spec.rb @@ -148,4 +148,18 @@ expect(organization.to_param).to eq('org_path') end end + + context 'on deleting organizations via SQL' do + it 'does not allow to delete default organization' do + expect { default_organization.delete }.to raise_error( + ActiveRecord::StatementInvalid, /Deletion of the default Organization is not allowed/ + ) + end + + it 'allows to delete any other organization' do + organization.delete + + expect(described_class.where(id: organization)).not_to exist + end + end end