/* * 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). Note that * if *not* checking as another role, the caller should pass InvalidOid rather * than GetUserId(). Otherwise the check for row_security = OFF is skipped, and * so we may falsely report that RLS is active when the user has bypassed it. * * 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) { HeapTuple tuple; Form_pg_class classform; bool relrowsecurity; Oid user_id = checkAsUser ? checkAsUser : GetUserId(); /* Nothing to do for built-in relations */ if (relid < FirstNormalObjectId) return RLS_NONE; tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); if (!HeapTupleIsValid(tuple)) return RLS_NONE; classform = (Form_pg_class) GETSTRUCT(tuple); relrowsecurity = classform->relrowsecurity; ReleaseSysCache(tuple); /* Nothing to do if the relation does not have RLS */ if (!relrowsecurity) return RLS_NONE; /* * Check permissions * * Table owners always bypass RLS. Note that superuser is always * considered an owner. Return RLS_NONE_ENV to indicate that this * decision depends on the environment (in this case, the user_id). */ if (pg_class_ownercheck(relid, user_id)) return RLS_NONE_ENV; /* * If the row_security GUC is 'off', check if the user has permission to * bypass RLS. row_security is always considered 'on' when querying * through a view or other cases where checkAsUser is valid. */ if (!row_security && !checkAsUser) { if (has_bypassrls_privilege(user_id)) /* OK to bypass */ return RLS_NONE_ENV; else if (noError) return RLS_ENABLED; else ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("insufficient privilege to bypass row security."))); } /* RLS should be fully enabled for this relation. */ return RLS_ENABLED; }
/* * 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). Note that * if *not* checking as another role, the caller should pass InvalidOid rather * than GetUserId(). Otherwise the check for row_security = OFF is skipped, and * so we may falsely report that RLS is active when the user has bypassed it. * * 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) { HeapTuple tuple; Form_pg_class classform; bool relrowsecurity; Oid user_id = checkAsUser ? checkAsUser : GetUserId(); /* Nothing to do for built-in relations */ if (relid < FirstNormalObjectId) return RLS_NONE; /* * Check if we have been told to explicitly skip RLS (perhaps because this * is a foreign key check) */ if (InRowLevelSecurityDisabled()) return RLS_NONE; tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); if (!HeapTupleIsValid(tuple)) return RLS_NONE; classform = (Form_pg_class) GETSTRUCT(tuple); relrowsecurity = classform->relrowsecurity; ReleaseSysCache(tuple); /* Nothing to do if the relation does not have RLS */ if (!relrowsecurity) return RLS_NONE; /* * Check permissions * * If the relation has row level security enabled and the row_security GUC * is off, then check if the user has rights to bypass RLS for this * relation. Table owners can always bypass, as can any role with the * BYPASSRLS capability. * * If the role is the table owner, then we bypass RLS unless row_security * is set to 'force'. Note that superuser is always considered an owner. * * Return RLS_NONE_ENV to indicate that this decision depends on the * environment (in this case, what the current values of user_id and * row_security are). */ if (row_security != ROW_SECURITY_FORCE && (pg_class_ownercheck(relid, user_id))) return RLS_NONE_ENV; /* * If the row_security GUC is 'off' then check if the user has permission * to bypass it. Note that we have already handled the case where the * user is the table owner above. * * Note that row_security is always considered 'on' when querying through * a view or other cases where checkAsUser is true, so skip this if * checkAsUser is in use. */ if (!checkAsUser && row_security == ROW_SECURITY_OFF) { if (has_bypassrls_privilege(user_id)) /* OK to bypass */ return RLS_NONE_ENV; else if (noError) return RLS_ENABLED; else ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("insufficient privilege to bypass row security."))); } /* RLS should be fully enabled for this relation. */ return RLS_ENABLED; }