| /*------------------------------------------------------------------------- |
| * |
| * rls.c |
| * RLS-related utility functions. |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/backend/utils/misc/rls.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/htup.h" |
| #include "access/htup_details.h" |
| #include "access/transam.h" |
| #include "catalog/namespace.h" |
| #include "catalog/pg_class.h" |
| #include "miscadmin.h" |
| #include "utils/acl.h" |
| #include "utils/builtins.h" |
| #include "utils/lsyscache.h" |
| #include "utils/rls.h" |
| #include "utils/syscache.h" |
| #include "utils/varlena.h" |
| |
| |
| /* |
| * check_enable_rls |
| * |
| * Determine, based on the relation, row_security setting, and current role, |
| * if RLS is applicable to this query. RLS_NONE_ENV indicates that, while |
| * RLS is not to be added for this query, a change in the environment may change |
| * that. RLS_NONE means that RLS is not on the relation at all and therefore |
| * we don't need to worry about it. RLS_ENABLED means RLS should be implemented |
| * for the table and the plan cache needs to be invalidated if the environment |
| * changes. |
| * |
| * Handle checking as another role via checkAsUser (for views, etc). Pass |
| * InvalidOid to check the current user. |
| * |
| * If noError is set to 'true' then we just return RLS_ENABLED instead of doing |
| * an ereport() if the user has attempted to bypass RLS and they are not |
| * allowed to. This allows users to check if RLS is enabled without having to |
| * deal with the actual error case (eg: error cases which are trying to decide |
| * if the user should get data from the relation back as part of the error). |
| */ |
| int |
| check_enable_rls(Oid relid, Oid checkAsUser, bool noError) |
| { |
| Oid user_id = OidIsValid(checkAsUser) ? checkAsUser : GetUserId(); |
| HeapTuple tuple; |
| Form_pg_class classform; |
| bool relrowsecurity; |
| bool relforcerowsecurity; |
| bool amowner; |
| |
| /* Nothing to do for built-in relations */ |
| if (relid < (Oid) FirstNormalObjectId) |
| return RLS_NONE; |
| |
| /* Fetch relation's relrowsecurity and relforcerowsecurity flags */ |
| tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); |
| if (!HeapTupleIsValid(tuple)) |
| return RLS_NONE; |
| classform = (Form_pg_class) GETSTRUCT(tuple); |
| |
| relrowsecurity = classform->relrowsecurity; |
| relforcerowsecurity = classform->relforcerowsecurity; |
| |
| ReleaseSysCache(tuple); |
| |
| /* Nothing to do if the relation does not have RLS */ |
| if (!relrowsecurity) |
| return RLS_NONE; |
| |
| /* |
| * BYPASSRLS users always bypass RLS. Note that superusers are always |
| * considered to have BYPASSRLS. |
| * |
| * Return RLS_NONE_ENV to indicate that this decision depends on the |
| * environment (in this case, the user_id). |
| */ |
| if (has_bypassrls_privilege(user_id)) |
| return RLS_NONE_ENV; |
| |
| /* |
| * Table owners generally bypass RLS, except if the table has been set (by |
| * an owner) to FORCE ROW SECURITY, and this is not a referential |
| * integrity check. |
| * |
| * Return RLS_NONE_ENV to indicate that this decision depends on the |
| * environment (in this case, the user_id). |
| */ |
| amowner = object_ownercheck(RelationRelationId, relid, user_id); |
| if (amowner) |
| { |
| /* |
| * If FORCE ROW LEVEL SECURITY has been set on the relation then we |
| * should return RLS_ENABLED to indicate that RLS should be applied. |
| * If not, or if we are in an InNoForceRLSOperation context, we return |
| * RLS_NONE_ENV. |
| * |
| * InNoForceRLSOperation indicates that we should not apply RLS even |
| * if the table has FORCE RLS set - IF the current user is the owner. |
| * This is specifically to ensure that referential integrity checks |
| * are able to still run correctly. |
| * |
| * This is intentionally only done after we have checked that the user |
| * is the table owner, which should always be the case for referential |
| * integrity checks. |
| */ |
| if (!relforcerowsecurity || InNoForceRLSOperation()) |
| return RLS_NONE_ENV; |
| } |
| |
| /* |
| * We should apply RLS. However, the user may turn off the row_security |
| * GUC to get a forced error instead. |
| */ |
| if (!row_security && !noError) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("query would be affected by row-level security policy for table \"%s\"", |
| get_rel_name(relid)), |
| amowner ? errhint("To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.") : 0)); |
| |
| /* RLS should be fully enabled for this relation. */ |
| return RLS_ENABLED; |
| } |
| |
| /* |
| * row_security_active |
| * |
| * check_enable_rls wrapped as a SQL callable function except |
| * RLS_NONE_ENV and RLS_NONE are the same for this purpose. |
| */ |
| Datum |
| row_security_active(PG_FUNCTION_ARGS) |
| { |
| /* By OID */ |
| Oid tableoid = PG_GETARG_OID(0); |
| int rls_status; |
| |
| rls_status = check_enable_rls(tableoid, InvalidOid, true); |
| PG_RETURN_BOOL(rls_status == RLS_ENABLED); |
| } |
| |
| Datum |
| row_security_active_name(PG_FUNCTION_ARGS) |
| { |
| /* By qualified name */ |
| text *tablename = PG_GETARG_TEXT_PP(0); |
| RangeVar *tablerel; |
| Oid tableoid; |
| int rls_status; |
| |
| /* Look up table name. Can't lock it - we might not have privileges. */ |
| tablerel = makeRangeVarFromNameList(textToQualifiedNameList(tablename)); |
| tableoid = RangeVarGetRelid(tablerel, NoLock, false); |
| |
| rls_status = check_enable_rls(tableoid, InvalidOid, true); |
| PG_RETURN_BOOL(rls_status == RLS_ENABLED); |
| } |