简体   繁体   中英

How can a set PostgreSQL schema on the fly using Doctrine and Symfony?

I'm trying to create a multi tenent app using Symfony 2.6 and PostgreSQL schemas (namespaces). I would like to know how can I change some entity schema on the pre persist event?

I know that it's possible to set the schema as annotation @Table(schema="schema") but this is static solution I need something more dynamic!

The purpose using PostgreSQL is take advantage of schemas feature like:

CREATE TABLE tenant_1.users (
    # table schema
);

CREATE TABLE tenant_2.users (
    # table schema
);

So, if I want only users from tenant_2 my query will be something like SELECT * FROM tenant_2.users;

This way my data will be separated and I will have only one database to connect and maintain.

$schema = sprintf('tenant_%d', $id);
$em->getConnection()->exec('SET search_path TO ' . $schema);

You might also want to involve PostgreSQL's row level security instead - that way you can actually prevent the tenant from accessing the data, not just hiding it by prefixing a schema path.

Check this one out: https://www.tangramvision.com/blog/hands-on-with-postgresql-authorization-part-2-row-level-security . I just set a working tenant separation with the information on that page and I'm quite excited about it.

In my case, my tenants are called organisations, and some (not all) tables have an organisation_id that permanently binds a row to it.

Here is a version of my script I run during a schema update, which finds all tables with column organisation_id and enables the row level security with a policy that only shows rows that an org owns, if the org role is set:

CREATE ROLE "org";

-- Find all tables with organisation_id and enable the row level security
DO $$ DECLARE
    r RECORD;
BEGIN
    FOR r IN (
        SELECT
            t.table_name, t.table_schema, c.column_name
        FROM
            information_schema.tables t
        INNER JOIN
            information_schema.columns c ON
                c.table_name = t.table_name
                AND c.table_schema = t.table_schema
                AND c.column_name = 'organisation_id'
        WHERE
            t.table_type = 'BASE TABLE'
            AND t.table_schema != 'information_schema'
            AND t.table_schema NOT LIKE 'pg_%'
    ) LOOP
        EXECUTE 'ALTER TABLE ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name) || ' ENABLE ROW LEVEL SECURITY';
        EXECUTE 'DROP POLICY IF EXISTS org_separation ON ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name);
        EXECUTE 'CREATE POLICY org_separation ON ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name) || 'FOR ALL to org USING (organisation_id = substr(current_user, 5)::int)';
    END LOOP;
END $$;

-- Grant usage on all tables in all schemas to the org role
DO $do$
DECLARE
    sch text;
BEGIN
    FOR sch IN (
        SELECT
            schema_name
        FROM
            information_schema.schemata
        WHERE
            schema_name != 'information_schema'
            AND schema_name NOT LIKE 'pg_%'
    ) LOOP
        EXECUTE format($$ GRANT USAGE ON SCHEMA %I TO org $$, sch);
        EXECUTE format($$ GRANT SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA %I TO org $$, sch);
        EXECUTE format($$ GRANT SELECT, UPDATE, INSERT, DELETE ON ALL TABLES IN SCHEMA %I TO org $$, sch);

        EXECUTE format($$ ALTER DEFAULT PRIVILEGES IN SCHEMA %I GRANT SELECT, UPDATE ON SEQUENCES TO org $$, sch);
        EXECUTE format($$ ALTER DEFAULT PRIVILEGES IN SCHEMA %I GRANT INSERT, SELECT, UPDATE, DELETE ON TABLES TO org $$, sch);
    END LOOP;
END;
$do$;

Step two, when I create a new organisation, I also create a role for it:

CREATE ROLE "org:86" LOGIN;
GRANT org TO "org:86";

Step three, at the beginning of every request that should be scoped to a particular organisation, I call SET ROLE "org:86"; to enable the restrictions.

There is much more happening around what we do with all of this, but the code above should be complete enough to help people get started.

Good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM