Grant access to subset of table to user on PostgreSQL - sql

I know that I can use views to grant access to a subset of attributes in a table. But how can I grant access to particular tuples only? Say I have a table of registered students, a username attribute and then some other like degree_status, how do I grant access so that user A can only select from the table a tuple corresponding to username A ? I have a database exam and I'm studying some past papers and I came across this question but I don't know how to answer it and I cant find how to do it from my book "Dtabase System: A practical Approach to Database Design, Implementation and Management'
Thanks any help is much appreciated!
Matt

Say that you got :
Table items (item_id, ...)
Table users (user_id, ...)
Table users_permissions( user_id, item_id, perm_type )
You could create a VIEW like this :
SELECT i.*, p.perm_type
FROM items JOIN users_permissions USING (item_id)
WHERE user_id = get_current_user_id();
Users can select from this view but not remove the WHERE and JOIN restricting the permissions.
The get_current_user_id() function is likely to be the major problem ;)

Along the lines of peufeu's answer, in Postgresql the current user name is available through the function current_user. So a view
CREATE VIEW available_bigtable AS
SELECT * FROM bigtable
WHERE username = current_user;
looks like it does what you need. Grant SELECT to everyone on the view, but to no one (except admins) on the underlying bigtable.

The Veil project provides a framework for row-level access control in PostgreSQL.

How about creating a function that takes the user id and returns the subset of rows he has access to?
CREATE FUNCTION user_items(integer) RETURNS SETOF items AS $$
SELECT * FROM items WHERE user_id = $1
$$ LANGUAGE SQL;
SELECT * FROM user_items(55); # 55 being the user id
edit Thinking about it more, this could cause quite a performance hit, as the user_id condition would be applied to the whole data set, prior to any other "user-land" conditions.
For example, SELECT * FROM user_items(55) WHERE id=45 would first filter the entire table for user items, and only than find the ID on that subset.
With views, the query planner can decide on the optimal order to evaluate the conditions (where he'll probably filter for the ID first, than for user id). When using a function like I suggested, postgres can't do that.

Related

Is it possible to inherit data from another table across schemas?

I'm trying to create a custom list of houses that a user can plot on a map. Each user can create houses, or edit the houses they've created. However there is a common set of houses that all users must have, and that can not be edited or deleted.
The users are divided into one schema per user, with a user.houses table listing all the custom houses for a single user.
Similarly, the public.houses table holds the houses available to every user. The structure of these tables are identical.
Is there a way to not duplicate the public.houses table data across all the user.houses tables?
I was able to use inheritance to ensure that the user.houses table exactly matches the public.houses table structure. Now I want to do this with the data as well.
INSERT INTO public.houses id VALUES (1), (2) ;
INSERT INTO user_a.houses id VALUES (3) ;
INSERT INTO user_b.houses id VALUES (4) ;
SELECT id FROM user_b.houses ;
I expect the output to be ids: [1, 2, 4]
This is too long for a comment.
You may have already made a bad decision by having one schema per user. There are some valid reasons for doing this -- security would be the number one reason. If you had a strong requirement that the data be separated. Another reason would be if the users were actually using different database versions (their own installations).
That said, it is usually better to support multiple users within a single databases/schema. The tables would identify the user for each entity (where it is important). Some tables, such as reference tables would be shared without ownership.
One thing that you can do is create views. The view:
create view v_user_houses as
select p.*
from public.houses p
union all
select u.*
from user.houses u;
You would need a separate view for each user.
An alternative would be to copy the publish houses into all the users table. When anything changes in the pubic houses, then you would propagate those changes to the users houses tables.
Try this
SELECT array_agg(a.id) FROM (
select id from public.houses
union all
select id from user_a.houses
union all
select id from user_b.houses ) AS a

Restrict what a user can see in an SQL query

this is a bit out there, but I was wondering if it is possible to restrict a usergroup in SQL to only see certain subsets of data..
Say for example a table of products, ProductName and State are the two columns. I only want a usergroup to see products from their state, when they do a query like SELECT * FROM Products
Is that possible?
Restrict access to the underlying table.
And give your user groups access to views on these underlying tables.
So:
Given table:
CREATE TABLE EVERYTHING ID INTEGER, TYPE CHAR(1), SEMISECRET CHAR(20), REALLYSECRECT CHAR(20));
You can create views which give access to only certain columns:-
CREATE VIEW SOMESTUFF AS SELECT ID, TYPE, SEMISECRET FROM EVERTHINK;
You can create views which give access to certain rows:-
CREATE VIEW TYPESLICE AS SELECT ID, TYPE, REALLYSECRECT WHERE TYPE ='X';
Much more is possible, but that's the basics.
Create one or more views corresponding to your subsets and set the appropriate permissions on them.

in postgres, is it possible to optimize a VIEW of UNIONs

in the database there are many identical schemas, cmp01..cmpa0
each schema has a users table
each schema's users table's primary key has its own unique range
for example, in cmp01.users the usr_id is between 0x01000000 and 0x01ffffffff.
is there any way I could define a VIEW global.users that is a union of each of the cmp*.union tables in such a way that, if querying by usr_id, the optimizer would head for the correct schema?
was thinking something like:
create view global.users as
select * from cmp01.users where usr_id between 0x01000000 and 0x01ffffffff
union all
select * from cmp02.users where usr_id between 0x02000000 and 0x02ffffffff
....
would this work? NO. EXPLAIN ANALYZE shows all schema used.
Is there an approach that might give good hints to the optimizer?
Why not create a table in a public schema that has all users in it, possibly with an extra column to store the source schema. Since the ids are globally unique, you could keep the id column unique:
create table all_users (
source_schema varchar(32),
usr_id int primary key,
-- other columns as per existing table(s)
);
Poluate the table by inserting all rows:
insert into all_users
select 'cmp01', * from cmp01.users union
select 'cmp02', * from cmp02.users union ...; -- etc
Use triggers to keep the table up to date.
It's not that hard to set up, and it will perform every well
What about creating a partitioned table? The master table would be created as global.users and it would be partitioned by the schema name.
That way you'd get the small user tables in each schema (including fast retrievals) provided you can create queries that PostgreSQL can optimize i.e. including the schema name in the where condition. You could also create a view in each schema that would hide the needed schema name to query the partitioned tables. I don't think it would work by specifying only the user_id. I fear that PostgreSQL's partitioning features are not smart enough for that.
Or use just one single table, and create views in each schema with an instead of trigger and limiting the result to that schema's users.
Try something like:
create view global.users as
select *
from (select 'cmp01' sel_schema, 0x01000000 usr_id_start, 0x01ffffffff usr_id_end
union all
select 'cmp02' sel_schema, 0x02000000 usr_id_start, 0x02ffffffff usr_id_end) s
join (select u1.*, 'cmp01' schema from cmp01.users u1
union all
select u2.*, 'cmp02' schema from cmp02.users u2) u
on s.sel_schema = u.schema
and include a condition like specified_usr_id between usr_id_start and usr_id_end when querying the view by a specified user ID.

How to change results in Access query?

I have a table, we'll call it users.
id username type
The type field is a two character type, so each user either has US, MO, AD. Not sure why the original developer did this, but I cannot change this method, unfortunately.
1 mike US
2 Tim AD
3 mark MO
Instead of returning US, AD... etc types in Access, I'd like the query to replace the US with USER, replace MO with MODERATOR, and replace AD with ADMIN.
Is this possible?
Kind of like SELECT * FROM USERS and then if statements within Access (if US, then USER). Not sure how to proceed?
Sure thing! Try something along these lines:
select [id], [username],
iif([type] = "US", "User",
iif([type] = "MO", "Moderator",
iif([type] = "AD", "Admin", "Unknown Type"
)
)
(Here's a link to the IIf function)
All things considered, though, you may want to normalize your data with a lookup table. So, for example, create a second table called, "UserType" with two columns: [Code] and [Name]. Then populate it with three records:
"US", "User"
"MO", "Moderator"
"AD", "Admin"
Then, within query designer, include both tables and join them by dragging the "type" column from your source table to the "[Code]" column on the [UserType] table. Then, select the [id] and [username] columns from the first table, and the [name] column from the lookup table. That way, if you need to add new user types, you don't have to worry about going back and modifying all of your queries.
If you had another table called user type that looked like this
UserType (typeid, description)
US USER
AD ADMIN
MO MODERATOR
it would be trivial
SELECT users.id, users.username, UserType.description as type
FROM
USERS
INNER JOIN UserType
ON USERS.Type = UserType.TypeID;
you could also use a VBA function, or nested IIF
You could also change your Look up Display Control to a Combo Box and set the row source type to value list
You can create a table for the US to USER, MO to MODERATOR etc mappings and use it in a join.
A lot easier to maintain, extend (i.e. different languages) than hard-coding with the drawback that you need to maintain your mapping table.
Sql Server has a construct called Case When but it's not available in MS Access.
Here is a workaround
I agree that you should have a table for this -- anything else verges on storing data in your SQL statements.
For completeness, let me mention Switch() and Choose(). The former takes an arbitrary number of value pairs, the latter takes an index and a list of values. The former works for strings or numbers, as with CASE WHEN, while Choose() works only with numbers.
Both are of limited utility, since use of them amounts to storing a lookup table in your SQL, but it's good to know about these things for cases where it's simply not feasible to add a lookup table.

django: sort table data according to other table data

I have read the (excellent) documentation but I can't figure out how
to do this.
I have a table which represents user friendships called
"Shortlist" (with fields "from_user" and "to_user": both Foreign Keys to the
classic auth_user table. Namely, from_user befriends to_user).
I have another table into which I log user events/actions (UserLog).
In this table there are many fields including a "timestamp" and of
course a "user" field (a foreign key to the auth_user
table)
how can I retrieve data from table "Shortlist" for a specific user
(from_user) ordered by the timestamp in UserLog table? Or how can I
retrieve Shortlist data for a specific from_user ordered by the total
number of "to_user" actions logged?
in other words I would like to retrieve all user's friends that are
most active or order them by most recent action first.
thanks for your answer, however I think I might need some more help. What I want to do is to order users according to their activity (most active user first). Activities are logged in another table.
for instance I want to do something like this (if possible)
select * from Users U, (select count (id) from UserLog L where U.id=L.user_id) as actions order by actions desc
I would also like to sort them according to their latest actions (user whith most recent action, first)
any ideas (or best practices) on how to do those things?
Orderring by another table is possible with related field syntax:
Example:
UserProfile.objects.all().order_by('user__username')
To calculate thing based on fields use annotations