Is there a way to give a user access to different columns on different rows of a table in PostgreSQL? - sql

Is there a way to give a PostgreSQL user access to a different columns on different rows of a table? For example in the following table, is there some combination of GRANT or POLICY or ??? that would only allow the user to view the "allowed" cells?
CREATE TABLE my_table (
id INT,
col_a TEXT,
col_b TEXT
);
INSERT INTO my_table VALUES
(1, 'allowed', 'allowed'),
(2, 'allowed', 'forbidden')
;
I think it can be done by splitting the columns in to different tables, but is it possible with only a single table?
One possible solution suggested by O. Jones - use a view:
CREATE OR REPLACE VIEW my_secure_view AS
SELECT
id,
col_a,
CASE WHEN id = 1 THEN col_b ELSE NULL END AS col_b
FROM my_table;
GRANT SELECT ON my_secure_view TO whatever_user;
REVOKE SELECT ON my_table FROM whatever_user;
Is there a better way?

You can do this by creating a VIEW containing the rows and columns you allow your user to see, then granting the user SELECT access to the view but not the underlying table.
For example
CREATE OR REPLACE VIEW my_secure_view AS
SELECT allowedcol1, allowedcol2
FROM my_table
WHERE col_b <> 'forbidden';
GRANT SELECT ON my_secure_view TO whatever_user;
REVOKE SELECT ON my_table TO whatever_user;
You can also, if you wish, write stored procedures and grant access to them to the users you choose, while revoking access to the underlying tables.

Related

How replicate data(all dml statement) from one table to another table in different schema oracle

I want to replicate table A data to B as we have some different columns in table B. So It is possible to replicate data.
Schema names are different also
example:
table A
(custome_name, customer_desc,create_date,create_user,update_date,update_user)
table B
(customer_id,custome_name, customer_desc,create_date,create_user,update_date,update_user)
User B has to grant privileges to user A so that A could insert data into their table:
grant insert on table_b to a;
Then, user A would write an INSERT statement, specifying which columns in user B's table_b will be populated with which column values from table_a
INSERT INTO b.table_b (custome_name,
customer_desc,
create_date,
create_user,
update_date,
update_user)
SELECT custome_name,
customer_desc,
create_date,
create_user,
update_date,
update_user
FROM table_a;
That's it.
If users reside in different databases, you'd use a database link.

Role to access the table only but not data from Table in Oracle Exadata

Users have create table statement with select statement from multiple tables from multiple schema. I want to restrict them to read data and allow them to create empty table in their schema with metadata only not data. This I want to do at user access and roles level.
Please tell me how I can do this?
I have tried giving them read access on underlying tables but users can see data as well.
Create table cust_acct_details
as
select *
from ep_rel.acct a
inner join ep_dnf.Cust_account ca
on a.acct_id = ca.acct_id
Tables should create without data.
Add below condition to your code
-- condition to add where 1<>1
Create table cust_acct_details
as
select *
from ep_rel.acct a
inner join ep_dnf.Cust_account ca
on a.acct_id = ca.acct_id
where 1<>1
Please make sure there are unique column names in your select statement. Oracle will not allow same column name in one table. Please use alias instead of *.
If you remove all tablespace privileges from a user they can still create tables but they won't be able to populate them.
For example, if you run this PL/SQL block to revoke all tablespace quotas from one user:
begin
for users in
(
select 'alter user '||username||' quota 0 on '||tablespace_name v_sql
from dba_ts_quotas
where username = 'TEST_USER'
order by 1
) loop
execute immediate users.v_sql;
end loop;
end;
/
Now the user can create tables but will get an error if they try to add rows:
SQL> create table test1(a number);
Table created.
SQL> insert into test1 values(1);
insert into test1 values(1)
*
ERROR at line 1:
ORA-01536: space quota exceeded for tablespace 'USERS'
For metadata, users can always see the metadata in their own schema. To allow them to view the metadata in other schema, run a grant like:
grant select_catalog_role to the_user;
Then that user can view the metadata either in the ALL_ data dictionary views, or using DBMS_METADATA.GET_DDL.

Using Sequence for inserting

I want to insert a new row in my table.
There I want to put the ID with the help of seq_name.nextval.
So how to know the sequence name for that particular table?
To use a sequence to generate IDs, you create it normally in the schema where the table is.
As user application user GEM_APP:
CREATE TABLE my_table (id NUMBER, col1 ...);
CREATE SEQUENCE my_seq;
The application user itself (and f.i. it's stored procedures) can use the sequence directly:
INSERT INTO my_table (id, col1) VALUES (my_seq.nextval, 'bla');
However, other users need the correct privileges. Usually, you grant select rights on the sequence to the same users or roles you grant insert rights on the table:
GRANT SELECT, INSERT ON my_table TO user_xy;
GRANT SELECT ON my_seq TO user_xy;
Then the other user can insert data into the table, but must qualify the schema:
INSERT INTO gem_app.my_table(id, col1) VALUES (gem_app.my_seq.nextval, 'bla');
You can create aliases to hide the schemas, some people like them, some not, but I would definitely not recommend to use PUBLIC synonyms as they are hard to control and create all kind of namespace clashes.

Row Level Security in Postgres on Normalized Tables

The premise
In documentation, Row Level Security seems great. Based on what I've read I can now stop creating views like this:
SELECT data.*
FROM data
JOIN user_data
ON data.id = user_data.data_id
AND user_data.role = CURRENT_ROLE
The great part is, Postgres has a great analysis for that view starting with an index scan then a hash join on the user_data table, exactly what we want to happen because it's crazy fast. Compare that with a my RLS implementation:
CREATE POLICY data_owner
ON data
FOR ALL
TO user
USING (
(
SELECT TRUE AS BOOL FROM (
SELECT data_id FROM user_data WHERE user_role = CURRENT_USER
) AS user_data WHERE user_data.data_id = data.id
) = true
)
WITH CHECK (TRUE);
This bummer of a policy executes the condition for each row in the data table, instead of optimizing by scoping the query to the rows which our CURRENT_USER has access to, like our view does. To be clear, that means select * from data hits every row in the data table.
The question
How do I write a policy with an inner select which doesn't test said select on every row in the target table. Said another way: how do I get RLS to run my policy on the target table before running the actual query on the result?
p.s. I've left this question someone vague and fiddle-less, mostly because sqlfiddle hasn't hit 9.5 yet. Let me know if I need to add more color or some gists to get my question across.
PostgreSQL may be able to generate a better plan if you phrase the policy like this:
...
USING (EXISTS
(SELECT data_id
FROM user_data
WHERE user_data.data_id = data.id
AND role = current_user
)
)
You should have a (PRIMARY KEY?) index ON user_data (role, data_id) to speed up nested loop joins.
But I think that it would be a better design to include the permission information in the data table itself, perhaps using the name[] type:
CREATE TABLE data(
id integer PRIMARY KEY,
val text,
acl name[] NOT NULL
);
INSERT INTO data VALUES (1, 'one', ARRAY[name 'laurenz', name 'advpg']);
INSERT INTO data VALUES (2, 'two', ARRAY[name 'advpg']);
INSERT INTO data VALUES (3, 'three', ARRAY[name 'laurenz']);
Then you can use a policy like this:
CREATE POLICY data_owner ON data FOR ALL TO PUBLIC
USING (acl #> ARRAY[current_user::name])
WITH CHECK (TRUE);
ALTER TABLE data ENABLE ROW LEVEL SECURITY;
ALTER TABLE data FORCE ROW LEVEL SECURITY;
When I SELECT, I get only the rows for which I have permission:
SELECT id, val FROM data;
id | val
----+-------
1 | one
3 | three
(2 rows)
You can define a GIN index to support that condition:
CREATE INDEX ON data USING gin (acl _name_ops);

Copying the values from specific columns from an SQL table to another table

I have two Tables: Users and Permission. They share 2 columns with the same name, so I need to copy values of those 2 columns from 'Users' to 'Permission'. However, since there is a third column on Permission that cannot go blank, it needs to be filled with the value '0' by default.
See the diagram for further understanding:
What SQL command should I use to perform this feat?
Thanks!
insert into permission( userid, login, permission )
select UserId, login, 0 from users