mirror of
https://github.com/postgres/postgres.git
synced 2025-10-25 13:17:41 +03:00
Foreign tables are a core component of SQL/MED. This commit does not provide a working SQL/MED infrastructure, because foreign tables cannot yet be queried. Support for foreign table scans will need to be added in a future patch. However, this patch creates the necessary system catalog structure, syntax support, and support for ancillary operations such as COMMENT and SECURITY LABEL. Shigeru Hanada, heavily revised by Robert Haas
390 lines
11 KiB
C
390 lines
11 KiB
C
/* -------------------------------------------------------------------------
|
|
*
|
|
* seclabel.c
|
|
* routines to support security label feature.
|
|
*
|
|
* Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/genam.h"
|
|
#include "access/heapam.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/indexing.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/pg_seclabel.h"
|
|
#include "commands/seclabel.h"
|
|
#include "miscadmin.h"
|
|
#include "utils/acl.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/tqual.h"
|
|
|
|
/*
|
|
* For most object types, the permissions-checking logic is simple enough
|
|
* that it makes sense to just include it in CommentObject(). However,
|
|
* attributes require a bit more checking.
|
|
*/
|
|
static void CheckAttributeSecLabel(Relation relation);
|
|
|
|
typedef struct
|
|
{
|
|
const char *provider_name;
|
|
check_object_relabel_type hook;
|
|
} LabelProvider;
|
|
|
|
static List *label_provider_list = NIL;
|
|
|
|
/*
|
|
* ExecSecLabelStmt --
|
|
*
|
|
* Apply a security label to a database object.
|
|
*/
|
|
void
|
|
ExecSecLabelStmt(SecLabelStmt *stmt)
|
|
{
|
|
LabelProvider *provider = NULL;
|
|
ObjectAddress address;
|
|
Relation relation;
|
|
ListCell *lc;
|
|
|
|
/*
|
|
* Find the named label provider, or if none specified, check whether
|
|
* there's exactly one, and if so use it.
|
|
*/
|
|
if (stmt->provider == NULL)
|
|
{
|
|
if (label_provider_list == NIL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("no security label providers have been loaded")));
|
|
if (lnext(list_head(label_provider_list)) != NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("must specify provider when multiple security label providers have been loaded")));
|
|
provider = (LabelProvider *) linitial(label_provider_list);
|
|
}
|
|
else
|
|
{
|
|
foreach (lc, label_provider_list)
|
|
{
|
|
LabelProvider *lp = lfirst(lc);
|
|
|
|
if (strcmp(stmt->provider, lp->provider_name) == 0)
|
|
{
|
|
provider = lp;
|
|
break;
|
|
}
|
|
}
|
|
if (provider == NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("security label provider \"%s\" is not loaded",
|
|
stmt->provider)));
|
|
}
|
|
|
|
/*
|
|
* Translate the parser representation which identifies this object
|
|
* into an ObjectAddress. get_object_address() will throw an error if
|
|
* the object does not exist, and will also acquire a lock on the
|
|
* target to guard against concurrent modifications.
|
|
*/
|
|
address = get_object_address(stmt->objtype, stmt->objname, stmt->objargs,
|
|
&relation, ShareUpdateExclusiveLock);
|
|
|
|
/* Privilege and integrity checks. */
|
|
switch (stmt->objtype)
|
|
{
|
|
case OBJECT_SEQUENCE:
|
|
case OBJECT_TABLE:
|
|
case OBJECT_VIEW:
|
|
case OBJECT_FOREIGN_TABLE:
|
|
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
|
|
RelationGetRelationName(relation));
|
|
break;
|
|
case OBJECT_COLUMN:
|
|
CheckAttributeSecLabel(relation);
|
|
break;
|
|
case OBJECT_TYPE:
|
|
if (!pg_type_ownercheck(address.objectId, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
|
|
format_type_be(address.objectId));
|
|
break;
|
|
case OBJECT_AGGREGATE:
|
|
case OBJECT_FUNCTION:
|
|
if (!pg_proc_ownercheck(address.objectId, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
|
|
NameListToString(stmt->objname));
|
|
break;
|
|
case OBJECT_SCHEMA:
|
|
if (!pg_namespace_ownercheck(address.objectId, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE,
|
|
strVal(linitial(stmt->objname)));
|
|
break;
|
|
case OBJECT_LANGUAGE:
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to comment on procedural language")));
|
|
break;
|
|
case OBJECT_LARGEOBJECT:
|
|
if (!pg_largeobject_ownercheck(address.objectId, GetUserId()))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be owner of large object %u",
|
|
address.objectId)));
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized object type: %d",
|
|
(int) stmt->objtype);
|
|
}
|
|
|
|
/* Provider gets control here, may throw ERROR to veto new label. */
|
|
(*provider->hook)(&address, stmt->label);
|
|
|
|
/* Apply new label. */
|
|
SetSecurityLabel(&address, provider->provider_name, stmt->label);
|
|
|
|
/*
|
|
* If get_object_address() opened the relation for us, we close it to keep
|
|
* the reference count correct - but we retain any locks acquired by
|
|
* get_object_address() until commit time, to guard against concurrent
|
|
* activity.
|
|
*/
|
|
if (relation != NULL)
|
|
relation_close(relation, NoLock);
|
|
}
|
|
|
|
/*
|
|
* GetSecurityLabel returns the security label for a database object for a
|
|
* given provider, or NULL if there is no such label.
|
|
*/
|
|
char *
|
|
GetSecurityLabel(const ObjectAddress *object, const char *provider)
|
|
{
|
|
Relation pg_seclabel;
|
|
ScanKeyData keys[4];
|
|
SysScanDesc scan;
|
|
HeapTuple tuple;
|
|
Datum datum;
|
|
bool isnull;
|
|
char *seclabel = NULL;
|
|
|
|
Assert(!IsSharedRelation(object->classId));
|
|
|
|
ScanKeyInit(&keys[0],
|
|
Anum_pg_seclabel_objoid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(object->objectId));
|
|
ScanKeyInit(&keys[1],
|
|
Anum_pg_seclabel_classoid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(object->classId));
|
|
ScanKeyInit(&keys[2],
|
|
Anum_pg_seclabel_objsubid,
|
|
BTEqualStrategyNumber, F_INT4EQ,
|
|
Int32GetDatum(object->objectSubId));
|
|
ScanKeyInit(&keys[3],
|
|
Anum_pg_seclabel_provider,
|
|
BTEqualStrategyNumber, F_TEXTEQ,
|
|
CStringGetTextDatum(provider));
|
|
|
|
pg_seclabel = heap_open(SecLabelRelationId, AccessShareLock);
|
|
|
|
scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true,
|
|
SnapshotNow, 4, keys);
|
|
|
|
tuple = systable_getnext(scan);
|
|
if (HeapTupleIsValid(tuple))
|
|
{
|
|
datum = heap_getattr(tuple, Anum_pg_seclabel_label,
|
|
RelationGetDescr(pg_seclabel), &isnull);
|
|
if (!isnull)
|
|
seclabel = TextDatumGetCString(datum);
|
|
}
|
|
systable_endscan(scan);
|
|
|
|
heap_close(pg_seclabel, AccessShareLock);
|
|
|
|
return seclabel;
|
|
}
|
|
|
|
/*
|
|
* SetSecurityLabel attempts to set the security label for the specified
|
|
* provider on the specified object to the given value. NULL means that any
|
|
* any existing label should be deleted.
|
|
*/
|
|
void
|
|
SetSecurityLabel(const ObjectAddress *object,
|
|
const char *provider, const char *label)
|
|
{
|
|
Relation pg_seclabel;
|
|
ScanKeyData keys[4];
|
|
SysScanDesc scan;
|
|
HeapTuple oldtup;
|
|
HeapTuple newtup = NULL;
|
|
Datum values[Natts_pg_seclabel];
|
|
bool nulls[Natts_pg_seclabel];
|
|
bool replaces[Natts_pg_seclabel];
|
|
|
|
/* Security labels on shared objects are not supported. */
|
|
Assert(!IsSharedRelation(object->classId));
|
|
|
|
/* Prepare to form or update a tuple, if necessary. */
|
|
memset(nulls, false, sizeof(nulls));
|
|
memset(replaces, false, sizeof(replaces));
|
|
values[Anum_pg_seclabel_objoid - 1] = ObjectIdGetDatum(object->objectId);
|
|
values[Anum_pg_seclabel_classoid - 1] = ObjectIdGetDatum(object->classId);
|
|
values[Anum_pg_seclabel_objsubid - 1] = Int32GetDatum(object->objectSubId);
|
|
values[Anum_pg_seclabel_provider - 1] = CStringGetTextDatum(provider);
|
|
if (label != NULL)
|
|
values[Anum_pg_seclabel_label - 1] = CStringGetTextDatum(label);
|
|
|
|
/* Use the index to search for a matching old tuple */
|
|
ScanKeyInit(&keys[0],
|
|
Anum_pg_seclabel_objoid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(object->objectId));
|
|
ScanKeyInit(&keys[1],
|
|
Anum_pg_seclabel_classoid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(object->classId));
|
|
ScanKeyInit(&keys[2],
|
|
Anum_pg_seclabel_objsubid,
|
|
BTEqualStrategyNumber, F_INT4EQ,
|
|
Int32GetDatum(object->objectSubId));
|
|
ScanKeyInit(&keys[3],
|
|
Anum_pg_seclabel_provider,
|
|
BTEqualStrategyNumber, F_TEXTEQ,
|
|
CStringGetTextDatum(provider));
|
|
|
|
pg_seclabel = heap_open(SecLabelRelationId, RowExclusiveLock);
|
|
|
|
scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true,
|
|
SnapshotNow, 4, keys);
|
|
|
|
oldtup = systable_getnext(scan);
|
|
if (HeapTupleIsValid(oldtup))
|
|
{
|
|
if (label == NULL)
|
|
simple_heap_delete(pg_seclabel, &oldtup->t_self);
|
|
else
|
|
{
|
|
replaces[Anum_pg_seclabel_label - 1] = true;
|
|
newtup = heap_modify_tuple(oldtup, RelationGetDescr(pg_seclabel),
|
|
values, nulls, replaces);
|
|
simple_heap_update(pg_seclabel, &oldtup->t_self, newtup);
|
|
}
|
|
}
|
|
systable_endscan(scan);
|
|
|
|
/* If we didn't find an old tuple, insert a new one */
|
|
if (newtup == NULL && label != NULL)
|
|
{
|
|
newtup = heap_form_tuple(RelationGetDescr(pg_seclabel),
|
|
values, nulls);
|
|
simple_heap_insert(pg_seclabel, newtup);
|
|
}
|
|
|
|
/* Update indexes, if necessary */
|
|
if (newtup != NULL)
|
|
{
|
|
CatalogUpdateIndexes(pg_seclabel, newtup);
|
|
heap_freetuple(newtup);
|
|
}
|
|
|
|
heap_close(pg_seclabel, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* DeleteSecurityLabel removes all security labels for an object (and any
|
|
* sub-objects, if applicable).
|
|
*/
|
|
void
|
|
DeleteSecurityLabel(const ObjectAddress *object)
|
|
{
|
|
Relation pg_seclabel;
|
|
ScanKeyData skey[3];
|
|
SysScanDesc scan;
|
|
HeapTuple oldtup;
|
|
int nkeys;
|
|
|
|
/* Security labels on shared objects are not supported. */
|
|
if (IsSharedRelation(object->classId))
|
|
return;
|
|
|
|
ScanKeyInit(&skey[0],
|
|
Anum_pg_seclabel_objoid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(object->objectId));
|
|
ScanKeyInit(&skey[1],
|
|
Anum_pg_seclabel_classoid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(object->classId));
|
|
if (object->objectSubId != 0)
|
|
{
|
|
ScanKeyInit(&skey[2],
|
|
Anum_pg_seclabel_objsubid,
|
|
BTEqualStrategyNumber, F_INT4EQ,
|
|
Int32GetDatum(object->objectSubId));
|
|
nkeys = 3;
|
|
}
|
|
else
|
|
nkeys = 2;
|
|
|
|
pg_seclabel = heap_open(SecLabelRelationId, RowExclusiveLock);
|
|
|
|
scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true,
|
|
SnapshotNow, nkeys, skey);
|
|
while (HeapTupleIsValid(oldtup = systable_getnext(scan)))
|
|
simple_heap_delete(pg_seclabel, &oldtup->t_self);
|
|
systable_endscan(scan);
|
|
|
|
heap_close(pg_seclabel, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* Check whether the user is allowed to comment on an attribute of the
|
|
* specified relation.
|
|
*/
|
|
static void
|
|
CheckAttributeSecLabel(Relation relation)
|
|
{
|
|
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
|
|
RelationGetRelationName(relation));
|
|
|
|
/*
|
|
* Allow security labels only on columns of tables, views, and composite
|
|
* types (which are the only relkinds for which pg_dump will dump labels).
|
|
*/
|
|
if (relation->rd_rel->relkind != RELKIND_RELATION &&
|
|
relation->rd_rel->relkind != RELKIND_VIEW &&
|
|
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
|
|
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"%s\" is not a table, view, composite type, or foreign table",
|
|
RelationGetRelationName(relation))));
|
|
}
|
|
|
|
void
|
|
register_label_provider(const char *provider_name, check_object_relabel_type hook)
|
|
{
|
|
LabelProvider *provider;
|
|
MemoryContext oldcxt;
|
|
|
|
oldcxt = MemoryContextSwitchTo(TopMemoryContext);
|
|
provider = palloc(sizeof(LabelProvider));
|
|
provider->provider_name = pstrdup(provider_name);
|
|
provider->hook = hook;
|
|
label_provider_list = lappend(label_provider_list, provider);
|
|
MemoryContextSwitchTo(oldcxt);
|
|
}
|