#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
+#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/rls.h"
#include "utils/ruleutils.h"
#include "utils/snapmgr.h"
+#include "utils/syscache.h"
#include "utils/tqual.h"
* form "(key_name, ...)=(key_value, ...)". This is currently used
* for building unique-constraint and exclusion-constraint error messages.
*
+ * Note that if the user does not have permissions to view all of the
+ * columns involved then a NULL is returned. Returning a partial key seems
+ * unlikely to be useful and we have no way to know which of the columns the
+ * user provided (unlike in ExecBuildSlotValueDescription).
+ *
* The passed-in values/nulls arrays are the "raw" input to the index AM,
* e.g. results of FormIndexDatum --- this is not necessarily what is stored
* in the index, but it's what the user perceives to be stored.
Datum *values, bool *isnull)
{
StringInfoData buf;
+ Form_pg_index idxrec;
+ HeapTuple ht_idx;
int natts = indexRelation->rd_rel->relnatts;
int i;
+ int keyno;
+ Oid indexrelid = RelationGetRelid(indexRelation);
+ Oid indrelid;
+ AclResult aclresult;
+
+ /*
+ * Check permissions- if the user does not have access to view all of the
+ * key columns then return NULL to avoid leaking data.
+ *
+ * First check if RLS is enabled for the relation. If so, return NULL
+ * to avoid leaking data.
+ *
+ * Next we need to check table-level SELECT access and then, if
+ * there is no access there, check column-level permissions.
+ */
+
+ /*
+ * Fetch the pg_index tuple by the Oid of the index
+ */
+ ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
+ if (!HeapTupleIsValid(ht_idx))
+ elog(ERROR, "cache lookup failed for index %u", indexrelid);
+ idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+
+ indrelid = idxrec->indrelid;
+ Assert(indexrelid == idxrec->indexrelid);
+
+ /* RLS check- if RLS is enabled then we don't return anything. */
+ if (check_enable_rls(indrelid, GetUserId(), true) == RLS_ENABLED)
+ {
+ ReleaseSysCache(ht_idx);
+ return NULL;
+ }
+
+ /* Table-level SELECT is enough, if the user has it */
+ aclresult = pg_class_aclcheck(indrelid, GetUserId(), ACL_SELECT);
+ if (aclresult != ACLCHECK_OK)
+ {
+ /*
+ * No table-level access, so step through the columns in the
+ * index and make sure the user has SELECT rights on all of them.
+ */
+ for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+ {
+ AttrNumber attnum = idxrec->indkey.values[keyno];
+
+ aclresult = pg_attribute_aclcheck(indrelid, attnum, GetUserId(),
+ ACL_SELECT);
+
+ if (aclresult != ACLCHECK_OK)
+ {
+ /* No access, so clean up and return */
+ ReleaseSysCache(ht_idx);
+ return NULL;
+ }
+ }
+ }
+ ReleaseSysCache(ht_idx);
initStringInfo(&buf);
appendStringInfo(&buf, "(%s)=(",
- pg_get_indexdef_columns(RelationGetRelid(indexRelation),
- true));
+ pg_get_indexdef_columns(indexrelid, true));
for (i = 0; i < natts; i++)
{
{
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
+ char *key_desc;
index_deform_tuple(itup, RelationGetDescr(rel),
values, isnull);
+
+ key_desc = BuildIndexValueDescription(rel, values,
+ isnull);
+
ereport(ERROR,
(errcode(ERRCODE_UNIQUE_VIOLATION),
errmsg("duplicate key value violates unique constraint \"%s\"",
RelationGetRelationName(rel)),
- errdetail("Key %s already exists.",
- BuildIndexValueDescription(rel,
- values, isnull)),
+ key_desc ? errdetail("Key %s already exists.",
+ key_desc) : 0,
errtableconstraint(heapRel,
RelationGetRelationName(rel))));
}
#include "parser/parse_relation.h"
#include "nodes/makefuncs.h"
#include "rewrite/rewriteHandler.h"
-#include "rewrite/rowsecurity.h"
#include "storage/fd.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/memutils.h"
#include "utils/portal.h"
#include "utils/rel.h"
+#include "utils/rls.h"
#include "utils/snapmgr.h"
int *defmap; /* array of default att numbers */
ExprState **defexprs; /* array of default att expressions */
bool volatile_defexprs; /* is any of defexprs volatile? */
+ List *range_table;
/*
* These variables are used to reduce overhead in textual COPY FROM.
Relation rel;
Oid relid;
Node *query = NULL;
+ RangeTblEntry *rte;
/* Disallow COPY to/from file or program except to superusers. */
if (!pipe && !superuser())
{
TupleDesc tupDesc;
AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT);
- RangeTblEntry *rte;
List *attnums;
ListCell *cur;
* If RLS is not enabled for this, then just fall through to the
* normal non-filtering relation handling.
*/
- if (check_enable_rls(rte->relid, InvalidOid) == RLS_ENABLED)
+ if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
{
SelectStmt *select;
ColumnRef *cr;
cstate = BeginCopyFrom(rel, stmt->filename, stmt->is_program,
stmt->attlist, stmt->options);
+ cstate->range_table = list_make1(rte);
*processed = CopyFrom(cstate); /* copy from file to database */
EndCopyFrom(cstate);
}
cstate = BeginCopyTo(rel, query, queryString, relid,
stmt->filename, stmt->is_program,
stmt->attlist, stmt->options);
+ cstate->range_table = list_make1(rte);
*processed = DoCopyTo(cstate); /* copy from database to file */
EndCopyTo(cstate);
}
estate->es_result_relations = resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
+ estate->es_range_table = cstate->range_table;
/* Set up a tuple slot too */
myslot = ExecInitExtraTupleSlot(estate);
#include "miscadmin.h"
#include "parser/parse_clause.h"
#include "rewrite/rewriteHandler.h"
-#include "rewrite/rowsecurity.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/rls.h"
#include "utils/snapmgr.h"
* be enabled here. We don't actually support that currently, so throw
* our own ereport(ERROR) if that happens.
*/
- if (check_enable_rls(intoRelationId, InvalidOid) == RLS_ENABLED)
+ if (check_enable_rls(intoRelationId, InvalidOid, false) == RLS_ENABLED)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
(errmsg("policies not yet implemented for this command"))));
elog(ERROR, "SPI_exec failed: %s", querybuf.data);
if (SPI_processed > 0)
{
+ /*
+ * Note that this ereport() is returning data to the user. Generally,
+ * we would want to make sure that the user has been granted access to
+ * this data. However, REFRESH MAT VIEW is only able to be run by the
+ * owner of the mat view (or a superuser) and therefore there is no
+ * need to check for access to data in the mat view.
+ */
ereport(ERROR,
(errcode(ERRCODE_CARDINALITY_VIOLATION),
errmsg("new data for \"%s\" contains duplicate rows without any null columns",
/* How many levels deep into trigger execution are we? */
static int MyTriggerDepth = 0;
+/*
+ * Note that this macro also exists in executor/execMain.c. There does not
+ * appear to be any good header to put it into, given the structures that
+ * it uses, so we let them be duplicated. Be sure to update both if one needs
+ * to be changed, however.
+ */
#define GetModifiedColumns(relinfo, estate) \
(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->modifiedCols)
#include "utils/acl.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/rls.h"
#include "utils/snapmgr.h"
#include "utils/tqual.h"
DestReceiver *dest);
static bool ExecCheckRTEPerms(RangeTblEntry *rte);
static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
-static char *ExecBuildSlotValueDescription(TupleTableSlot *slot,
+static char *ExecBuildSlotValueDescription(Oid reloid,
+ TupleTableSlot *slot,
TupleDesc tupdesc,
+ Bitmapset *modifiedCols,
int maxfieldlen);
static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
Plan *planTree);
+/*
+ * Note that this macro also exists in commands/trigger.c. There does not
+ * appear to be any good header to put it into, given the structures that
+ * it uses, so we let them be duplicated. Be sure to update both if one needs
+ * to be changed, however.
+ */
+#define GetModifiedColumns(relinfo, estate) \
+ (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->modifiedCols)
+
/* end of local decls */
{
if (tupdesc->attrs[attrChk - 1]->attnotnull &&
slot_attisnull(slot, attrChk))
+ {
+ char *val_desc;
+ Bitmapset *modifiedCols;
+
+ modifiedCols = GetModifiedColumns(resultRelInfo, estate);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupdesc,
+ modifiedCols,
+ 64);
+
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("null value in column \"%s\" violates not-null constraint",
NameStr(tupdesc->attrs[attrChk - 1]->attname)),
- errdetail("Failing row contains %s.",
- ExecBuildSlotValueDescription(slot,
- tupdesc,
- 64)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
errtablecol(rel, attrChk)));
+ }
}
}
const char *failed;
if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
+ {
+ char *val_desc;
+ Bitmapset *modifiedCols;
+
+ modifiedCols = GetModifiedColumns(resultRelInfo, estate);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupdesc,
+ modifiedCols,
+ 64);
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
RelationGetRelationName(rel), failed),
- errdetail("Failing row contains %s.",
- ExecBuildSlotValueDescription(slot,
- tupdesc,
- 64)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
errtableconstraint(rel, failed)));
+ }
}
}
ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
{
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ TupleDesc tupdesc = RelationGetDescr(rel);
ExprContext *econtext;
ListCell *l1,
*l2;
* case (the opposite of what we do above for CHECK constraints).
*/
if (!ExecQual((List *) wcoExpr, econtext, false))
+ {
+ char *val_desc;
+ Bitmapset *modifiedCols;
+
+ modifiedCols = GetModifiedColumns(resultRelInfo, estate);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupdesc,
+ modifiedCols,
+ 64);
+
ereport(ERROR,
(errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION),
errmsg("new row violates WITH CHECK OPTION for \"%s\"",
wco->viewname),
- errdetail("Failing row contains %s.",
- ExecBuildSlotValueDescription(slot,
- RelationGetDescr(resultRelInfo->ri_RelationDesc),
- 64))));
+ val_desc ? errdetail("Failing row contains %s.", val_desc) :
+ 0));
+ }
}
}
* dropped columns. We used to use the slot's tuple descriptor to decode the
* data, but the slot's descriptor doesn't identify dropped columns, so we
* now need to be passed the relation's descriptor.
+ *
+ * Note that, like BuildIndexValueDescription, if the user does not have
+ * permission to view any of the columns involved, a NULL is returned. Unlike
+ * BuildIndexValueDescription, if the user has access to view a subset of the
+ * column involved, that subset will be returned with a key identifying which
+ * columns they are.
*/
static char *
-ExecBuildSlotValueDescription(TupleTableSlot *slot,
+ExecBuildSlotValueDescription(Oid reloid,
+ TupleTableSlot *slot,
TupleDesc tupdesc,
+ Bitmapset *modifiedCols,
int maxfieldlen)
{
StringInfoData buf;
+ StringInfoData collist;
bool write_comma = false;
+ bool write_comma_collist = false;
int i;
+ AclResult aclresult;
+ bool table_perm = false;
+ bool any_perm = false;
- /* Make sure the tuple is fully deconstructed */
- slot_getallattrs(slot);
+ /*
+ * Check if RLS is enabled and should be active for the relation; if so,
+ * then don't return anything. Otherwise, go through normal permission
+ * checks.
+ */
+ if (check_enable_rls(reloid, GetUserId(), true) == RLS_ENABLED)
+ return NULL;
initStringInfo(&buf);
appendStringInfoChar(&buf, '(');
+ /*
+ * Check if the user has permissions to see the row. Table-level SELECT
+ * allows access to all columns. If the user does not have table-level
+ * SELECT then we check each column and include those the user has SELECT
+ * rights on. Additionally, we always include columns the user provided
+ * data for.
+ */
+ aclresult = pg_class_aclcheck(reloid, GetUserId(), ACL_SELECT);
+ if (aclresult != ACLCHECK_OK)
+ {
+ /* Set up the buffer for the column list */
+ initStringInfo(&collist);
+ appendStringInfoChar(&collist, '(');
+ }
+ else
+ table_perm = any_perm = true;
+
+ /* Make sure the tuple is fully deconstructed */
+ slot_getallattrs(slot);
+
for (i = 0; i < tupdesc->natts; i++)
{
+ bool column_perm = false;
char *val;
int vallen;
if (tupdesc->attrs[i]->attisdropped)
continue;
- if (slot->tts_isnull[i])
- val = "null";
- else
+ if (!table_perm)
{
- Oid foutoid;
- bool typisvarlena;
+ /*
+ * No table-level SELECT, so need to make sure they either have
+ * SELECT rights on the column or that they have provided the
+ * data for the column. If not, omit this column from the error
+ * message.
+ */
+ aclresult = pg_attribute_aclcheck(reloid, tupdesc->attrs[i]->attnum,
+ GetUserId(), ACL_SELECT);
+ if (bms_is_member(tupdesc->attrs[i]->attnum - FirstLowInvalidHeapAttributeNumber,
+ modifiedCols) || aclresult == ACLCHECK_OK)
+ {
+ column_perm = any_perm = true;
- getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
- &foutoid, &typisvarlena);
- val = OidOutputFunctionCall(foutoid, slot->tts_values[i]);
- }
+ if (write_comma_collist)
+ appendStringInfoString(&collist, ", ");
+ else
+ write_comma_collist = true;
- if (write_comma)
- appendStringInfoString(&buf, ", ");
- else
- write_comma = true;
+ appendStringInfoString(&collist, NameStr(tupdesc->attrs[i]->attname));
+ }
+ }
- /* truncate if needed */
- vallen = strlen(val);
- if (vallen <= maxfieldlen)
- appendStringInfoString(&buf, val);
- else
+ if (table_perm || column_perm)
{
- vallen = pg_mbcliplen(val, vallen, maxfieldlen);
- appendBinaryStringInfo(&buf, val, vallen);
- appendStringInfoString(&buf, "...");
+ if (slot->tts_isnull[i])
+ val = "null";
+ else
+ {
+ Oid foutoid;
+ bool typisvarlena;
+
+ getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
+ &foutoid, &typisvarlena);
+ val = OidOutputFunctionCall(foutoid, slot->tts_values[i]);
+ }
+
+ if (write_comma)
+ appendStringInfoString(&buf, ", ");
+ else
+ write_comma = true;
+
+ /* truncate if needed */
+ vallen = strlen(val);
+ if (vallen <= maxfieldlen)
+ appendStringInfoString(&buf, val);
+ else
+ {
+ vallen = pg_mbcliplen(val, vallen, maxfieldlen);
+ appendBinaryStringInfo(&buf, val, vallen);
+ appendStringInfoString(&buf, "...");
+ }
}
}
+ /* If we end up with zero columns being returned, then return NULL. */
+ if (!any_perm)
+ return NULL;
+
appendStringInfoChar(&buf, ')');
+ if (!table_perm)
+ {
+ appendStringInfoString(&collist, ") = ");
+ appendStringInfoString(&collist, buf.data);
+
+ return collist.data;
+ }
+
return buf.data;
}
(errcode(ERRCODE_EXCLUSION_VIOLATION),
errmsg("could not create exclusion constraint \"%s\"",
RelationGetRelationName(index)),
- errdetail("Key %s conflicts with key %s.",
- error_new, error_existing),
+ error_new && error_existing ?
+ errdetail("Key %s conflicts with key %s.",
+ error_new, error_existing) :
+ errdetail("Key conflicts exist."),
errtableconstraint(heap,
RelationGetRelationName(index))));
else
(errcode(ERRCODE_EXCLUSION_VIOLATION),
errmsg("conflicting key value violates exclusion constraint \"%s\"",
RelationGetRelationName(index)),
- errdetail("Key %s conflicts with existing key %s.",
- error_new, error_existing),
+ error_new && error_existing ?
+ errdetail("Key %s conflicts with existing key %s.",
+ error_new, error_existing) :
+ errdetail("Key conflicts with existing key."),
errtableconstraint(heap,
RelationGetRelationName(index))));
}
#include "utils/acl.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/rls.h"
#include "utils/syscache.h"
#include "tcop/utility.h"
return false;
/* Determine the state of RLS for this, pass checkAsUser explicitly */
- rls_status = check_enable_rls(rte->relid, rte->checkAsUser);
+ rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
/* If there is no RLS on this table at all, nothing to do */
if (rls_status == RLS_NONE)
return;
}
-/*
- * 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).
- */
-int
-check_enable_rls(Oid relid, Oid checkAsUser)
-{
- HeapTuple tuple;
- Form_pg_class classform;
- bool relrowsecurity;
- Oid user_id = checkAsUser ? checkAsUser : GetUserId();
-
- 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
- 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_role_for_policy -
* determines if the policy should be applied for the current role
#include "parser/parse_coerce.h"
#include "parser/parse_relation.h"
#include "miscadmin.h"
+#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
+#include "utils/rls.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
bool onfk;
const int16 *attnums;
int idx;
+ Oid rel_oid;
+ AclResult aclresult;
+ bool has_perm = true;
if (spi_err)
ereport(ERROR,
if (onfk)
{
attnums = riinfo->fk_attnums;
+ rel_oid = fk_rel->rd_id;
if (tupdesc == NULL)
tupdesc = fk_rel->rd_att;
}
else
{
attnums = riinfo->pk_attnums;
+ rel_oid = pk_rel->rd_id;
if (tupdesc == NULL)
tupdesc = pk_rel->rd_att;
}
- /* Get printable versions of the keys involved */
- initStringInfo(&key_names);
- initStringInfo(&key_values);
- for (idx = 0; idx < riinfo->nkeys; idx++)
+ /*
+ * Check permissions- if the user does not have access to view the data in
+ * any of the key columns then we don't include the errdetail() below.
+ *
+ * Check if RLS is enabled on the relation first. If so, we don't return
+ * any specifics to avoid leaking data.
+ *
+ * Check table-level permissions next and, failing that, column-level
+ * privileges.
+ */
+
+ if (check_enable_rls(rel_oid, GetUserId(), true) != RLS_ENABLED)
{
- int fnum = attnums[idx];
- char *name,
- *val;
+ aclresult = pg_class_aclcheck(rel_oid, GetUserId(), ACL_SELECT);
+ if (aclresult != ACLCHECK_OK)
+ {
+ /* Try for column-level permissions */
+ for (idx = 0; idx < riinfo->nkeys; idx++)
+ {
+ aclresult = pg_attribute_aclcheck(rel_oid, attnums[idx],
+ GetUserId(),
+ ACL_SELECT);
- name = SPI_fname(tupdesc, fnum);
- val = SPI_getvalue(violator, tupdesc, fnum);
- if (!val)
- val = "null";
+ /* No access to the key */
+ if (aclresult != ACLCHECK_OK)
+ {
+ has_perm = false;
+ break;
+ }
+ }
+ }
+ }
- if (idx > 0)
+ if (has_perm)
+ {
+ /* Get printable versions of the keys involved */
+ initStringInfo(&key_names);
+ initStringInfo(&key_values);
+ for (idx = 0; idx < riinfo->nkeys; idx++)
{
- appendStringInfoString(&key_names, ", ");
- appendStringInfoString(&key_values, ", ");
+ int fnum = attnums[idx];
+ char *name,
+ *val;
+
+ name = SPI_fname(tupdesc, fnum);
+ val = SPI_getvalue(violator, tupdesc, fnum);
+ if (!val)
+ val = "null";
+
+ if (idx > 0)
+ {
+ appendStringInfoString(&key_names, ", ");
+ appendStringInfoString(&key_values, ", ");
+ }
+ appendStringInfoString(&key_names, name);
+ appendStringInfoString(&key_values, val);
}
- appendStringInfoString(&key_names, name);
- appendStringInfoString(&key_values, val);
}
if (onfk)
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(fk_rel),
NameStr(riinfo->conname)),
- errdetail("Key (%s)=(%s) is not present in table \"%s\".",
- key_names.data, key_values.data,
- RelationGetRelationName(pk_rel)),
+ has_perm ?
+ errdetail("Key (%s)=(%s) is not present in table \"%s\".",
+ key_names.data, key_values.data,
+ RelationGetRelationName(pk_rel)) :
+ errdetail("Key is not present in table \"%s\".",
+ RelationGetRelationName(pk_rel)),
errtableconstraint(fk_rel, NameStr(riinfo->conname))));
else
ereport(ERROR,
RelationGetRelationName(pk_rel),
NameStr(riinfo->conname),
RelationGetRelationName(fk_rel)),
+ has_perm ?
errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
key_names.data, key_values.data,
+ RelationGetRelationName(fk_rel)) :
+ errdetail("Key is still referenced from table \"%s\".",
RelationGetRelationName(fk_rel)),
errtableconstraint(fk_rel, NameStr(riinfo->conname))));
}
#include "optimizer/prep.h"
#include "parser/analyze.h"
#include "parser/parsetree.h"
-#include "rewrite/rowsecurity.h"
#include "storage/lmgr.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
#include "utils/inval.h"
#include "utils/memutils.h"
#include "utils/resowner_private.h"
+#include "utils/rls.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
-OBJS = guc.o help_config.o pg_rusage.o ps_status.o \
+OBJS = guc.o help_config.o pg_rusage.o ps_status.o rls.o \
superuser.o timeout.o tzparser.o
# This location might depend on the installation directories. Therefore
#include "replication/syncrep.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
-#include "rewrite/rowsecurity.h"
#include "storage/bufmgr.h"
#include "storage/dsm_impl.h"
#include "storage/standby.h"
#include "utils/plancache.h"
#include "utils/portal.h"
#include "utils/ps_status.h"
+#include "utils/rls.h"
#include "utils/snapmgr.h"
#include "utils/tzparser.h"
#include "utils/xml.h"
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * rls.c
+ * RLS-related utility functions.
+ *
+ * Portions Copyright (c) 1996-2015, 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 "catalog/pg_class.h"
+#include "miscadmin.h"
+#include "utils/acl.h"
+#include "utils/elog.h"
+#include "utils/rls.h"
+#include "utils/syscache.h"
+
+
+extern int check_enable_rls(Oid relid, Oid checkAsUser, bool noError);
+
+/*
+ * 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).
+ *
+ * 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();
+
+ 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;
+}
{
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
+ char *key_desc;
/*
* Some rather brain-dead implementations of qsort (such as the one in
Assert(tuple1 != tuple2);
index_deform_tuple(tuple1, tupDes, values, isnull);
+
+ key_desc = BuildIndexValueDescription(state->indexRel, values, isnull);
+
ereport(ERROR,
(errcode(ERRCODE_UNIQUE_VIOLATION),
errmsg("could not create unique index \"%s\"",
RelationGetRelationName(state->indexRel)),
- errdetail("Key %s is duplicated.",
- BuildIndexValueDescription(state->indexRel,
- values, isnull)),
+ key_desc ? errdetail("Key %s is duplicated.", key_desc) :
+ errdetail("Duplicate keys exist."),
errtableconstraint(state->heapRel,
RelationGetRelationName(state->indexRel))));
}
List *policies; /* list of row security policies */
} RowSecurityDesc;
-/* GUC variable */
-extern int row_security;
-
-/* Possible values for row_security GUC */
-typedef enum RowSecurityConfigType
-{
- ROW_SECURITY_OFF, /* RLS never applied- error thrown if no priv */
- ROW_SECURITY_ON, /* normal case, RLS applied for regular users */
- ROW_SECURITY_FORCE /* RLS applied for superusers and table owners */
-} RowSecurityConfigType;
-
-/*
- * Used by callers of check_enable_rls.
- *
- * RLS could be completely disabled on the tables involved in the query,
- * which is the simple case, or it may depend on the current environment
- * (the role which is running the query or the value of the row_security
- * GUC- on, off, or force), or it might be simply enabled as usual.
- *
- * If RLS isn't on the table involved then RLS_NONE is returned to indicate
- * that we don't need to worry about invalidating the query plan for RLS
- * reasons. If RLS is on the table, but we are bypassing it for now, then
- * we return RLS_NONE_ENV to indicate that, if the environment changes,
- * we need to invalidate and replan. Finally, if RLS should be turned on
- * for the query, then we return RLS_ENABLED, which means we also need to
- * invalidate if the environment changes.
- */
-enum CheckEnableRlsResult
-{
- RLS_NONE,
- RLS_NONE_ENV,
- RLS_ENABLED
-};
-
typedef List *(*row_security_policy_hook_type)(CmdType cmdtype,
Relation relation);
extern bool prepend_row_security_policies(Query* root, RangeTblEntry* rte,
int rt_index);
-extern int check_enable_rls(Oid relid, Oid checkAsUser);
-
#endif /* ROWSECURITY_H */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * rls.h
+ * Header file for Row Level Security (RLS) utility commands to be used
+ * with the rowsecurity feature.
+ *
+ * Copyright (c) 2007-2015, PostgreSQL Global Development Group
+ *
+ * src/include/utils/rls.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef RLS_H
+#define RLS_H
+
+/* GUC variable */
+extern int row_security;
+
+/* Possible values for row_security GUC */
+typedef enum RowSecurityConfigType
+{
+ ROW_SECURITY_OFF, /* RLS never applied- error thrown if no priv */
+ ROW_SECURITY_ON, /* normal case, RLS applied for regular users */
+ ROW_SECURITY_FORCE /* RLS applied for superusers and table owners */
+} RowSecurityConfigType;
+
+/*
+ * Used by callers of check_enable_rls.
+ *
+ * RLS could be completely disabled on the tables involved in the query,
+ * which is the simple case, or it may depend on the current environment
+ * (the role which is running the query or the value of the row_security
+ * GUC- on, off, or force), or it might be simply enabled as usual.
+ *
+ * If RLS isn't on the table involved then RLS_NONE is returned to indicate
+ * that we don't need to worry about invalidating the query plan for RLS
+ * reasons. If RLS is on the table, but we are bypassing it for now, then
+ * we return RLS_NONE_ENV to indicate that, if the environment changes,
+ * we need to invalidate and replan. Finally, if RLS should be turned on
+ * for the query, then we return RLS_ENABLED, which means we also need to
+ * invalidate if the environment changes.
+ *
+ * Note that RLS_ENABLED will also be returned if noError is true
+ * (indicating that the caller simply want to know if RLS should be applied
+ * for this user but doesn't want an error thrown if it is; this is used
+ * by other error cases where we're just trying to decide if data from the
+ * table should be passed back to the user or not).
+ */
+enum CheckEnableRlsResult
+{
+ RLS_NONE,
+ RLS_NONE_ENV,
+ RLS_ENABLED
+};
+
+extern int check_enable_rls(Oid relid, Oid checkAsUser, bool noError);
+
+#endif /* RLS_H */
(0 rows)
COPY atest6 TO stdout; -- ok
+-- check error reporting with column privs
+SET SESSION AUTHORIZATION regressuser1;
+CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
+GRANT SELECT (c1) ON t1 TO regressuser2;
+GRANT INSERT (c1, c2, c3) ON t1 TO regressuser2;
+GRANT UPDATE (c1, c2, c3) ON t1 TO regressuser2;
+-- seed data
+INSERT INTO t1 VALUES (1, 1, 1);
+INSERT INTO t1 VALUES (1, 2, 1);
+INSERT INTO t1 VALUES (2, 1, 2);
+INSERT INTO t1 VALUES (2, 2, 2);
+INSERT INTO t1 VALUES (3, 1, 3);
+SET SESSION AUTHORIZATION regressuser2;
+INSERT INTO t1 (c1, c2) VALUES (1, 1); -- fail, but row not shown
+ERROR: duplicate key value violates unique constraint "t1_pkey"
+UPDATE t1 SET c2 = 1; -- fail, but row not shown
+ERROR: duplicate key value violates unique constraint "t1_pkey"
+INSERT INTO t1 (c1, c2) VALUES (null, null); -- fail, but see columns being inserted
+ERROR: null value in column "c1" violates not-null constraint
+DETAIL: Failing row contains (c1, c2) = (null, null).
+INSERT INTO t1 (c3) VALUES (null); -- fail, but see columns being inserted or have SELECT
+ERROR: null value in column "c1" violates not-null constraint
+DETAIL: Failing row contains (c1, c3) = (null, null).
+INSERT INTO t1 (c1) VALUES (5); -- fail, but see columns being inserted or have SELECT
+ERROR: null value in column "c2" violates not-null constraint
+DETAIL: Failing row contains (c1) = (5).
+UPDATE t1 SET c3 = 10; -- fail, but see columns with SELECT rights, or being modified
+ERROR: new row for relation "t1" violates check constraint "t1_c3_check"
+DETAIL: Failing row contains (c1, c3) = (1, 10).
+SET SESSION AUTHORIZATION regressuser1;
+DROP TABLE t1;
-- test column-level privileges when involved with DELETE
SET SESSION AUTHORIZATION regressuser1;
ALTER TABLE atest6 ADD COLUMN three integer;
SET SESSION AUTHORIZATION rls_regress_user1;
INSERT INTO document VALUES (8, 44, 1, 'rls_regress_user1', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see
ERROR: duplicate key value violates unique constraint "document_pkey"
-DETAIL: Key (did)=(8) already exists.
SELECT * FROM document WHERE did = 8; -- and confirm we can't see it
did | cid | dlevel | dauthor | dtitle
-----+-----+--------+---------+--------
WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail
ERROR: new row violates WITH CHECK OPTION for "t1"
-DETAIL: Failing row contains (1, cfcd208495d565ef66e7dff9f98764da).
WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok
a | b
----+----------------------------------
WITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail
ERROR: new row violates WITH CHECK OPTION for "t1"
-DETAIL: Failing row contains (21, Fail).
WITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok
a | b
----+---------
SELECT atest6 FROM atest6; -- ok
COPY atest6 TO stdout; -- ok
+-- check error reporting with column privs
+SET SESSION AUTHORIZATION regressuser1;
+CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
+GRANT SELECT (c1) ON t1 TO regressuser2;
+GRANT INSERT (c1, c2, c3) ON t1 TO regressuser2;
+GRANT UPDATE (c1, c2, c3) ON t1 TO regressuser2;
+
+-- seed data
+INSERT INTO t1 VALUES (1, 1, 1);
+INSERT INTO t1 VALUES (1, 2, 1);
+INSERT INTO t1 VALUES (2, 1, 2);
+INSERT INTO t1 VALUES (2, 2, 2);
+INSERT INTO t1 VALUES (3, 1, 3);
+
+SET SESSION AUTHORIZATION regressuser2;
+INSERT INTO t1 (c1, c2) VALUES (1, 1); -- fail, but row not shown
+UPDATE t1 SET c2 = 1; -- fail, but row not shown
+INSERT INTO t1 (c1, c2) VALUES (null, null); -- fail, but see columns being inserted
+INSERT INTO t1 (c3) VALUES (null); -- fail, but see columns being inserted or have SELECT
+INSERT INTO t1 (c1) VALUES (5); -- fail, but see columns being inserted or have SELECT
+UPDATE t1 SET c3 = 10; -- fail, but see columns with SELECT rights, or being modified
+
+SET SESSION AUTHORIZATION regressuser1;
+DROP TABLE t1;
+
-- test column-level privileges when involved with DELETE
SET SESSION AUTHORIZATION regressuser1;
ALTER TABLE atest6 ADD COLUMN three integer;