diff --git a/scripts-available/CDB_ForeignTable.sql b/scripts-available/CDB_ForeignTable.sql index c86f5ef..b9449de 100644 --- a/scripts-available/CDB_ForeignTable.sql +++ b/scripts-available/CDB_ForeignTable.sql @@ -139,176 +139,6 @@ $$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE; --- Produce a valid DB name for objects created for the user FDW's -CREATE OR REPLACE FUNCTION @extschema@.__CDB_User_FDW_Object_Names(fdw_input_name NAME) -RETURNS NAME AS $$ - -- Note on input we use %s and on output we use %I, in order to - -- avoid double escaping - SELECT format('cdb_fdw_%s', fdw_input_name)::NAME; -$$ -LANGUAGE sql IMMUTABLE PARALLEL SAFE; - --- A function to set up a user-defined foreign data server --- It does not read from CDB_Conf. --- Only superuser roles can invoke it successfully --- --- Sample call: --- SELECT cartodb.CDB_SetUp_User_PG_FDW_Server('amazon', '{ --- "server": { --- "extensions": "postgis", --- "dbname": "testdb", --- "host": "myhostname.us-east-2.rds.amazonaws.com", --- "port": "5432" --- }, --- "user_mapping": { --- "user": "fdw_user", --- "password": "secret" --- } --- }'); --- --- Underneath it will: --- * Set up postgresql_fdw --- * Create a server with the name 'cdb_fdw_amazon' --- * Create a role called 'cdb_fdw_amazon' to manage access --- * Create a user mapping with that role 'cdb_fdw_amazon' --- * Create a schema 'cdb_fdw_amazon' as a convenience to set up all foreign --- tables over there --- --- It is the responsibility of the superuser to grant that role to either: --- * Nobody --- * Specific roles: GRANT amazon TO role_name; --- * Members of the organization: SELECT cartodb.CDB_Organization_Grant_Role('cdb_fdw_amazon'); --- * The publicuser: GRANT cdb_fdw_amazon TO publicuser; -CREATE OR REPLACE FUNCTION @extschema@._CDB_SetUp_User_PG_FDW_Server(fdw_input_name NAME, config json) -RETURNS void AS $$ -DECLARE - row record; - option record; - fdw_objects_name NAME := @extschema@.__CDB_User_FDW_Object_Names(fdw_input_name); -BEGIN - -- TODO: refactor with original function - -- This function tries to be as idempotent as possible, by not creating anything more than once - -- (not even using IF NOT EXIST to avoid throwing warnings) - IF NOT EXISTS ( SELECT * FROM pg_extension WHERE extname = 'postgres_fdw') THEN - CREATE EXTENSION postgres_fdw; - RAISE NOTICE 'Created postgres_fdw EXTENSION'; - END IF; - -- Create FDW first if it does not exist - IF NOT EXISTS ( SELECT * FROM pg_foreign_server WHERE srvname = fdw_objects_name) - THEN - EXECUTE FORMAT('CREATE SERVER %I FOREIGN DATA WRAPPER postgres_fdw', fdw_objects_name); - RAISE NOTICE 'Created SERVER % using postgres_fdw', fdw_objects_name; - END IF; - - -- Set FDW settings - FOR row IN SELECT p.key, p.value from lateral json_each_text(config->'server') p - LOOP - IF NOT EXISTS (WITH a AS (select split_part(unnest(srvoptions), '=', 1) as options from pg_foreign_server where srvname=fdw_objects_name) SELECT * from a where options = row.key) - THEN - EXECUTE FORMAT('ALTER SERVER %I OPTIONS (ADD %I %L)', fdw_objects_name, row.key, row.value); - ELSE - EXECUTE FORMAT('ALTER SERVER %I OPTIONS (SET %I %L)', fdw_objects_name, row.key, row.value); - END IF; - END LOOP; - - -- Create specific role for this - IF NOT EXISTS ( SELECT 1 FROM pg_roles WHERE rolname = fdw_objects_name) THEN - EXECUTE format('CREATE ROLE %I NOLOGIN', fdw_objects_name); - RAISE NOTICE 'Created special ROLE % to access the correponding FDW', fdw_objects_name; - END IF; - - -- Transfer ownership of the server to the fdw role - EXECUTE format('ALTER SERVER %I OWNER TO %I', fdw_objects_name, fdw_objects_name); - - -- Create user mapping - -- NOTE: we use a PUBLIC user mapping but control access to the SERVER - -- so that we don't need to create a mapping for every user nor store credentials elsewhere - IF NOT EXISTS ( SELECT * FROM pg_user_mappings WHERE srvname = fdw_objects_name AND usename = 'public' ) THEN - EXECUTE FORMAT ('CREATE USER MAPPING FOR public SERVER %I', fdw_objects_name); - RAISE NOTICE 'Created USER MAPPING for accesing foreign server %', fdw_objects_name; - END IF; - - -- Update user mapping settings - FOR option IN SELECT o.key, o.value from lateral json_each_text(config->'user_mapping') o LOOP - IF NOT EXISTS (WITH a AS (select split_part(unnest(umoptions), '=', 1) as options from pg_user_mappings WHERE srvname = fdw_objects_name AND usename = 'public') SELECT * from a where options = option.key) THEN - EXECUTE FORMAT('ALTER USER MAPPING FOR PUBLIC SERVER %I OPTIONS (ADD %I %L)', fdw_objects_name, option.key, option.value); - ELSE - EXECUTE FORMAT('ALTER USER MAPPING FOR PUBLIC SERVER %I OPTIONS (SET %I %L)', fdw_objects_name, option.key, option.value); - END IF; - END LOOP; - - -- Grant usage on the wrapper and server to the fdw role - EXECUTE FORMAT ('GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO %I', fdw_objects_name); - RAISE NOTICE 'Granted USAGE on the postgres_fdw to the role %', fdw_objects_name; - EXECUTE FORMAT ('GRANT USAGE ON FOREIGN SERVER %I TO %I', fdw_objects_name, fdw_objects_name); - RAISE NOTICE 'Granted USAGE on the foreign server to the role %', fdw_objects_name; - - -- Create schema if it does not exist. - IF NOT EXISTS ( SELECT * from pg_namespace WHERE nspname=fdw_objects_name) THEN - EXECUTE FORMAT ('CREATE SCHEMA %I', fdw_objects_name); - RAISE NOTICE 'Created SCHEMA % to host foreign tables', fdw_objects_name; - END IF; - - -- Give the fdw role ownership over the schema - EXECUTE FORMAT ('ALTER SCHEMA %I OWNER TO %I', fdw_objects_name, fdw_objects_name); - RAISE NOTICE 'Gave ownership on the SCHEMA % to %', fdw_objects_name, fdw_objects_name; - - -- TODO: Bring here the remote cdb_tablemetadata -END -$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE; - - --- A function to drop a user-defined foreign server and all related objects --- It does not read from CDB_Conf --- It must be executed with a superuser role to succeed --- --- Sample call: --- SELECT cartodb.CDB_Drop_User_PG_FDW_Server('amazon') --- --- Note: if there's any dependent object (i.e. foreign table) this call will fail -CREATE OR REPLACE FUNCTION @extschema@._CDB_Drop_User_PG_FDW_Server(fdw_input_name NAME, force boolean = false) -RETURNS void AS $$ -DECLARE - fdw_objects_name NAME := @extschema@.__CDB_User_FDW_Object_Names(fdw_input_name); - cascade_clause NAME; -BEGIN - CASE force - WHEN true THEN - cascade_clause := 'CASCADE'; - ELSE - cascade_clause := 'RESTRICT'; - END CASE; - - EXECUTE FORMAT ('DROP SCHEMA %I %s', fdw_objects_name, cascade_clause); - RAISE NOTICE 'Dropped schema %', fdw_objects_name; - EXECUTE FORMAT ('DROP USER MAPPING FOR public SERVER %I', fdw_objects_name); - RAISE NOTICE 'Dropped user mapping for server %', fdw_objects_name; - EXECUTE FORMAT ('DROP SERVER %I %s', fdw_objects_name, cascade_clause); - RAISE NOTICE 'Dropped foreign server %', fdw_objects_name; - EXECUTE FORMAT ('REVOKE USAGE ON FOREIGN DATA WRAPPER postgres_fdw FROM %I %s', fdw_objects_name, cascade_clause); - RAISE NOTICE 'Revoked usage on postgres_fdw from %', fdw_objects_name; - EXECUTE FORMAT ('DROP ROLE %I', fdw_objects_name); - RAISE NOTICE 'Dropped role %', fdw_objects_name; -END -$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE; - - --- Set up a user foreign table --- E.g: --- SELECT cartodb.CDB_SetUp_User_PG_FDW_Table('amazon', 'carto_lite', 'mytable'); --- SELECT * FROM amazon.my_table; -CREATE OR REPLACE FUNCTION @extschema@.CDB_SetUp_User_PG_FDW_Table(fdw_input_name NAME, foreign_schema NAME, table_name NAME) -RETURNS void AS $$ -DECLARE - fdw_objects_name NAME := @extschema@.__CDB_User_FDW_Object_Names(fdw_input_name); -BEGIN - EXECUTE FORMAT ('IMPORT FOREIGN SCHEMA %I LIMIT TO (%I) FROM SERVER %I INTO %I;', foreign_schema, table_name, fdw_objects_name, fdw_objects_name); - --- Grant SELECT to fdw role - EXECUTE FORMAT ('GRANT SELECT ON %I.%I TO %I;', fdw_objects_name, table_name, fdw_objects_name); -END -$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE; - - CREATE OR REPLACE FUNCTION @extschema@._cdb_dbname_of_foreign_table(reloid oid) RETURNS TEXT AS $$ SELECT option_value FROM pg_options_to_table(( @@ -374,3 +204,12 @@ RETURNS timestamptz AS $$ LEFT JOIN pg_catalog.pg_class c ON c.oid = reloid ) SELECT max(updated_at) FROM t_updated_at; $$ LANGUAGE SQL VOLATILE PARALLEL UNSAFE; + + +-------------------------------------------------------------------------------- +-- Deprecated +-------------------------------------------------------------------------------- +DROP FUNCTION IF EXISTS @extschema@.__CDB_User_FDW_Object_Names(name); +DROP FUNCTION IF EXISTS @extschema@._CDB_SetUp_User_PG_FDW_Server(name, json); +DROP FUNCTION IF EXISTS @extschema@._CDB_Drop_User_PG_FDW_Server(name, boolean); +DROP FUNCTION IF EXISTS @extschema@.CDB_SetUp_User_PG_FDW_Table(name, name, name); diff --git a/scripts-available/CDB_Organizations.sql b/scripts-available/CDB_Organizations.sql index c532ed5..94e228e 100644 --- a/scripts-available/CDB_Organizations.sql +++ b/scripts-available/CDB_Organizations.sql @@ -172,27 +172,7 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE; -------------------------------------------------------------------------------- --- Role management +-- Deprecated -------------------------------------------------------------------------------- -CREATE OR REPLACE -FUNCTION @extschema@.CDB_Organization_Grant_Role(role_name name) -RETURNS VOID AS $$ -DECLARE - org_role TEXT; -BEGIN - org_role := @extschema@.CDB_Organization_Member_Group_Role_Member_Name(); - EXECUTE format('GRANT %I TO %I', role_name, org_role); -END -$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE; - - -CREATE OR REPLACE -FUNCTION @extschema@.CDB_Organization_Revoke_Role(role_name name) -RETURNS VOID AS $$ -DECLARE - org_role TEXT; -BEGIN - org_role := @extschema@.CDB_Organization_Member_Group_Role_Member_Name(); - EXECUTE format('REVOKE %I FROM %I', role_name, org_role); -END -$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE; +DROP FUNCTION IF EXISTS @extschema@.CDB_Organization_Grant_Role(name); +DROP FUNCTION IF EXISTS @extschema@.CDB_Organization_Revoke_Role(name); diff --git a/test/extension/test.sh b/test/extension/test.sh index faf3d93..aab19f3 100755 --- a/test/extension/test.sh +++ b/test/extension/test.sh @@ -598,68 +598,6 @@ test_extension|public|"local-table-with-dashes"' sql postgres "DROP FOREIGN TABLE IF EXISTS test_fdw.cdb_tablemetadata;" sql postgres "SELECT cartodb.CDB_Get_Foreign_Updated_At('test_fdw.foo') IS NULL" should 't' - - # Check user-defined FDW's - # Set up a user foreign server - read -d '' ufdw_config <<- EOF -{ - "server": { - "extensions": "postgis", - "dbname": "fdw_target", - "host": "localhost", - "port": ${PGPORT:-5432} - }, - "user_mapping": { - "user": "fdw_user", - "password": "foobarino" - } -} -EOF - sql postgres "SELECT cartodb._CDB_SetUp_User_PG_FDW_Server('user-defined-test', '$ufdw_config');" - - # Grant a user access to that FDW, and to grant to others - sql postgres 'GRANT "cdb_fdw_user-defined-test" TO cdb_testmember_1 WITH ADMIN OPTION;' - - # Set up a user foreign table - sql cdb_testmember_1 "SELECT cartodb.CDB_SetUp_User_PG_FDW_Table('user-defined-test', 'test_fdw', 'foo');" - - # Check that the table can be accessed by the owner/creator - sql cdb_testmember_1 'SELECT * from "cdb_fdw_user-defined-test".foo;' - sql cdb_testmember_1 'SELECT a from "cdb_fdw_user-defined-test".foo LIMIT 1;' should 42 - - # Check that a role with no permissions cannot use the FDW to access a remote table - sql cdb_testmember_2 'IMPORT FOREIGN SCHEMA test_fdw LIMIT TO (foo) FROM SERVER "cdb_fdw_user-defined-test" INTO public' fails - - # Check that the table can be accessed by some other user by granting the role - sql cdb_testmember_2 'SELECT a from "cdb_fdw_user-defined-test".foo LIMIT 1;' fails - sql cdb_testmember_1 'GRANT "cdb_fdw_user-defined-test" TO cdb_testmember_2;' - sql cdb_testmember_2 'SELECT a from "cdb_fdw_user-defined-test".foo LIMIT 1;' should 42 - sql cdb_testmember_1 'REVOKE "cdb_fdw_user-defined-test" FROM cdb_testmember_2;' - - # Check that the table can be accessed by org members - sql cdb_testmember_2 'SELECT a from "cdb_fdw_user-defined-test".foo LIMIT 1;' fails - sql cdb_testmember_1 "SELECT cartodb.CDB_Organization_Grant_Role('cdb_fdw_user-defined-test');" - sql cdb_testmember_2 'SELECT a from "cdb_fdw_user-defined-test".foo LIMIT 1;' should 42 - sql cdb_testmember_1 "SELECT cartodb.CDB_Organization_Revoke_Role('cdb_fdw_user-defined-test');" - - # By default publicuser cannot access the FDW - sql publicuser 'SELECT a from "cdb_fdw_user-defined-test".foo LIMIT 1;' fails - sql cdb_testmember_1 'GRANT "cdb_fdw_user-defined-test" TO publicuser;' # but can be granted - sql publicuser 'SELECT a from "cdb_fdw_user-defined-test".foo LIMIT 1;' should 42 - sql cdb_testmember_1 'REVOKE "cdb_fdw_user-defined-test" FROM publicuser;' - - # If there are dependent objects, we cannot drop the foreign server - sql postgres "SELECT cartodb._CDB_Drop_User_PG_FDW_Server('user-defined-test')" fails - sql cdb_testmember_1 'DROP FOREIGN TABLE "cdb_fdw_user-defined-test".foo;' - sql postgres "SELECT cartodb._CDB_Drop_User_PG_FDW_Server('user-defined-test')" - - # But if there are, we can set the force flag to true to drop everything (defaults to false) - sql postgres "SELECT cartodb._CDB_SetUp_User_PG_FDW_Server('another_user_defined_test', '$ufdw_config');" - sql postgres 'GRANT cdb_fdw_another_user_defined_test TO cdb_testmember_1 WITH ADMIN OPTION;' - sql cdb_testmember_1 "SELECT cartodb.CDB_SetUp_User_PG_FDW_Table('another_user_defined_test', 'test_fdw', 'foo');" - sql postgres "SELECT cartodb._CDB_Drop_User_PG_FDW_Server('another_user_defined_test', /* force = */ true)" - - # Teardown DATABASE=fdw_target sql postgres 'REVOKE USAGE ON SCHEMA test_fdw FROM fdw_user;' DATABASE=fdw_target sql postgres 'REVOKE SELECT ON test_fdw.foo FROM fdw_user;'