Stephen, Thanks for the feedback.
> > diff --git a/src/backend/access/transam/xlogfuncs.c > b/src/backend/access/transam/xlogfuncs.c > > --- 56,62 ---- > > > > backupidstr = text_to_cstring(backupid); > > > > ! if (!superuser() && !check_role_attribute(GetUserId(), > ROLE_ATTR_REPLICATION)) > > ereport(ERROR, > > (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), > > errmsg("must be superuser or replication role to run a > backup"))); > > The point of has_role_attribute() was to avoid the need to explicitly > say "!superuser()" everywhere. The idea with check_role_attribute() is > that we want to present the user (in places like pg_roles) with the > values that are *actually* set. > > In other words, the above should just be: > > if (!has_role_attribute(GetUserId(), ROLE_ATTR_REPLICATION)) > Yes, I understand. My original thought here was that I was replacing 'has_rolreplication' which didn't perform any superuser check and that I was trying to adhere to minimal changes. But I agree this would be the appropriate solution. Fixed. > > > + /* > > + * check_role_attribute > > + * Check if the role with the specified id has been assigned a > specific > > + * role attribute. This function does not allow any superuser > bypass. > > I don't think we need to say that it doesn't "allow" superuser bypass. > Instead, I'd comment that has_role_attribute() should be used for > permissions checks while check_role_attribute is for checking what the > value in the rolattr bitmap is and isn't for doing permissions checks > directly. > Ok. Understood. Fixed. > diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c > > *************** pg_role_aclcheck(Oid role_oid, Oid rolei > > *** 4602,4607 **** > > --- 4603,4773 ---- > > return ACLCHECK_NO_PRIV; > > } > > > > + /* > > + * pg_has_role_attribute_id_attr > > I'm trying to figure out what the point of the trailing "_attr" in the > function name is..? Doesn't seem necessary to have that for these > functions and it'd look a bit cleaner without it, imv. > So, I was trying to follow what I perceived as the following convention for these functions: pg_<function_name>_<arg1>_<arg2>_<argN>. So, what "_attr" represents is the second argument of the function which is the attribute to check. I could agree that might be unnecessary, but that was my thought process on it. At any rate, I've removed it. > ! #define ROLE_ATTR_ALL 255 /* or (1 << N_ROLE_ATTRIBUTES) - 1 */ > > I'd say "equals" or something rather than "or" since that kind of > implies that it's an laternative, but we can't do that as discussed in > the comment (which I like). > Agreed. Fixed. > > ! /* role attribute check routines */ > > ! extern bool has_role_attribute(Oid roleid, RoleAttr attribute); > > ! extern bool check_role_attribute(Oid roleid, RoleAttr attribute); > > With all the 'has_role_attribute(GetUserId(), ROLEATTR_BLAH)' cases, I'd > suggest doing the same as 'superuser()' and also provide a simpler > version: 'have_role_attribute(ROLEATTR_BLAH)' which handles doing the > GetUserId() itself. That'd simplify quite a few of the above calls. > Good point. Added. Attached is an updated patch. -Adam -- Adam Brightwell - adam.brightw...@crunchydatasolutions.com Database Engineer - www.crunchydatasolutions.com
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c new file mode 100644 index 133143d..8475985 *** a/src/backend/access/transam/xlogfuncs.c --- b/src/backend/access/transam/xlogfuncs.c *************** *** 22,32 **** --- 22,34 ---- #include "access/xlog_internal.h" #include "access/xlogutils.h" #include "catalog/catalog.h" + #include "catalog/pg_authid.h" #include "catalog/pg_type.h" #include "funcapi.h" #include "miscadmin.h" #include "replication/walreceiver.h" #include "storage/smgr.h" + #include "utils/acl.h" #include "utils/builtins.h" #include "utils/numeric.h" #include "utils/guc.h" *************** pg_start_backup(PG_FUNCTION_ARGS) *** 54,60 **** backupidstr = text_to_cstring(backupid); ! if (!superuser() && !has_rolreplication(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser or replication role to run a backup"))); --- 56,62 ---- backupidstr = text_to_cstring(backupid); ! if (!have_role_attribute(ROLE_ATTR_REPLICATION)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser or replication role to run a backup"))); *************** pg_stop_backup(PG_FUNCTION_ARGS) *** 82,88 **** { XLogRecPtr stoppoint; ! if (!superuser() && !has_rolreplication(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser or replication role to run a backup")))); --- 84,90 ---- { XLogRecPtr stoppoint; ! if (!have_role_attribute(ROLE_ATTR_REPLICATION)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser or replication role to run a backup")))); diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c new file mode 100644 index d30612c..db08f93 *** a/src/backend/catalog/aclchk.c --- b/src/backend/catalog/aclchk.c *************** aclcheck_error_type(AclResult aclerr, Oi *** 3423,3448 **** } - /* Check if given user has rolcatupdate privilege according to pg_authid */ - static bool - has_rolcatupdate(Oid roleid) - { - bool rolcatupdate; - HeapTuple tuple; - - tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); - if (!HeapTupleIsValid(tuple)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("role with OID %u does not exist", roleid))); - - rolcatupdate = ((Form_pg_authid) GETSTRUCT(tuple))->rolcatupdate; - - ReleaseSysCache(tuple); - - return rolcatupdate; - } - /* * Relay for the various pg_*_mask routines depending on object kind */ --- 3423,3428 ---- *************** pg_class_aclmask(Oid table_oid, Oid role *** 3630,3636 **** if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) && IsSystemClass(table_oid, classForm) && classForm->relkind != RELKIND_VIEW && ! !has_rolcatupdate(roleid) && !allowSystemTableMods) { #ifdef ACLDEBUG --- 3610,3616 ---- if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) && IsSystemClass(table_oid, classForm) && classForm->relkind != RELKIND_VIEW && ! !has_role_attribute(roleid, ROLE_ATTR_CATUPDATE) && !allowSystemTableMods) { #ifdef ACLDEBUG *************** pg_extension_ownercheck(Oid ext_oid, Oid *** 5051,5102 **** } /* ! * Check whether specified role has CREATEROLE privilege (or is a superuser) * ! * Note: roles do not have owners per se; instead we use this test in ! * places where an ownership-like permissions test is needed for a role. ! * Be sure to apply it to the role trying to do the operation, not the ! * role being operated on! Also note that this generally should not be ! * considered enough privilege if the target role is a superuser. ! * (We don't handle that consideration here because we want to give a ! * separate error message for such cases, so the caller has to deal with it.) */ bool ! has_createrole_privilege(Oid roleid) { ! bool result = false; ! HeapTuple utup; ! ! /* Superusers bypass all permission checking. */ ! if (superuser_arg(roleid)) return true; ! utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); ! if (HeapTupleIsValid(utup)) ! { ! result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole; ! ReleaseSysCache(utup); ! } ! return result; } bool ! has_bypassrls_privilege(Oid roleid) { ! bool result = false; ! HeapTuple utup; ! /* Superusers bypass all permission checking. */ ! if (superuser_arg(roleid)) ! return true; ! utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); ! if (HeapTupleIsValid(utup)) ! { ! result = ((Form_pg_authid) GETSTRUCT(utup))->rolbypassrls; ! ReleaseSysCache(utup); ! } ! return result; } /* --- 5031,5102 ---- } /* ! * has_role_attribute ! * Check if the role with the specified id has been assigned a specific role ! * attribute. * ! * roleid - the oid of the role to check. ! * attribute - the attribute to check. ! * ! * Note: Use this function for role attribute permission checking as it accounts ! * for superuser status. It will always return true for roles with superuser ! * privileges unless the attribute being checked is CATUPDATE (superusers are not ! * allowed to bypass CATUPDATE permissions). */ bool ! has_role_attribute(Oid roleid, RoleAttr attribute) { ! /* ! * Superusers bypass all permission checking except in the case of CATUPDATE. ! */ ! if (!(attribute & ROLE_ATTR_CATUPDATE) && superuser_arg(roleid)) return true; ! return check_role_attribute(roleid, attribute); } + /* + * have_role_attribute + * Convenience function for checking if the role id returned by GetUserId() has + * been assigned a specific role attribute. + * + * attribute - the attribute to check. + */ bool ! have_role_attribute(RoleAttr attribute) { ! return has_role_attribute(GetUserId(), attribute); ! } ! /* ! * check_role_attribute ! * Check if the role with the specified id has been assigned a specific role ! * attribute. ! * ! * roleid - the oid of the role to check. ! * attribute - the attribute to check. ! * ! * Note: This function should only be used for checking the value of an individual ! * attribute in the rolattr bitmap and should *not* be used for permission checking. ! * For the purposes of permission checking use 'has_role_attribute' instead. ! */ ! bool ! check_role_attribute(Oid roleid, RoleAttr attribute) ! { ! RoleAttr attributes; ! HeapTuple tuple; ! tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); ! ! if (!HeapTupleIsValid(tuple)) ! ereport(ERROR, ! (errcode(ERRCODE_UNDEFINED_OBJECT), ! errmsg("role with OID %u does not exist", roleid))); ! ! attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr; ! ReleaseSysCache(tuple); ! ! return (attributes & attribute); } /* diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl new file mode 100644 index ca89879..2929b66 *** a/src/backend/catalog/genbki.pl --- b/src/backend/catalog/genbki.pl *************** my $BOOTSTRAP_SUPERUSERID = *** 90,95 **** --- 90,97 ---- find_defined_symbol('pg_authid.h', 'BOOTSTRAP_SUPERUSERID'); my $PG_CATALOG_NAMESPACE = find_defined_symbol('pg_namespace.h', 'PG_CATALOG_NAMESPACE'); + my $ROLE_ATTR_ALL = + find_defined_symbol('pg_authid.h', 'ROLE_ATTR_ALL'); # Read all the input header files into internal data structures my $catalogs = Catalog::Catalogs(@input_files); *************** foreach my $catname (@{ $catalogs->{name *** 144,149 **** --- 146,152 ---- # substitute constant values we acquired above $row->{bki_values} =~ s/\bPGUID\b/$BOOTSTRAP_SUPERUSERID/g; $row->{bki_values} =~ s/\bPGNSP\b/$PG_CATALOG_NAMESPACE/g; + $row->{bki_values} =~ s/\bPGROLATTRALL/$ROLE_ATTR_ALL/g; # Save pg_type info for pg_attribute processing below if ($catname eq 'pg_type') diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql new file mode 100644 index a036c62..79a16b1 *** a/src/backend/catalog/information_schema.sql --- b/src/backend/catalog/information_schema.sql *************** CREATE VIEW user_mapping_options AS *** 2884,2890 **** CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name, CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user) OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE')) ! OR (SELECT rolsuper FROM pg_authid WHERE rolname = current_user) THEN (pg_options_to_table(um.umoptions)).option_value ELSE NULL END AS character_data) AS option_value FROM _pg_user_mappings um; --- 2884,2895 ---- CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name, CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user) OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE')) ! OR ( ! SELECT pg_check_role_attribute(pg_authid.oid, 'SUPERUSER') AS rolsuper ! FROM pg_authid ! WHERE rolname = current_user ! ) ! THEN (pg_options_to_table(um.umoptions)).option_value ELSE NULL END AS character_data) AS option_value FROM _pg_user_mappings um; diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c new file mode 100644 index e261307..6df7e4d *** a/src/backend/catalog/objectaddress.c --- b/src/backend/catalog/objectaddress.c *************** check_object_ownership(Oid roleid, Objec *** 1310,1316 **** } else { ! if (!has_createrole_privilege(roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must have CREATEROLE privilege"))); --- 1310,1316 ---- } else { ! if (!has_role_attribute(roleid, ROLE_ATTR_CREATEROLE)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must have CREATEROLE privilege"))); diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql new file mode 100644 index 22b8cee..cb0e193 *** a/src/backend/catalog/system_views.sql --- b/src/backend/catalog/system_views.sql *************** *** 9,25 **** CREATE VIEW pg_roles AS SELECT rolname, ! rolsuper, ! rolinherit, ! rolcreaterole, ! rolcreatedb, ! rolcatupdate, ! rolcanlogin, ! rolreplication, rolconnlimit, '********'::text as rolpassword, rolvaliduntil, - rolbypassrls, setconfig as rolconfig, pg_authid.oid FROM pg_authid LEFT JOIN pg_db_role_setting s --- 9,25 ---- CREATE VIEW pg_roles AS SELECT rolname, ! pg_check_role_attribute(pg_authid.oid, 'SUPERUSER') AS rolsuper, ! pg_check_role_attribute(pg_authid.oid, 'INHERIT') AS rolinherit, ! pg_check_role_attribute(pg_authid.oid, 'CREATEROLE') AS rolcreaterole, ! pg_check_role_attribute(pg_authid.oid, 'CREATEDB') AS rolcreatedb, ! pg_check_role_attribute(pg_authid.oid, 'CATUPDATE') AS rolcatupdate, ! pg_check_role_attribute(pg_authid.oid, 'CANLOGIN') AS rolcanlogin, ! pg_check_role_attribute(pg_authid.oid, 'REPLICATION') AS rolreplication, ! pg_check_role_attribute(pg_authid.oid, 'BYPASSRLS') AS rolbypassrls, rolconnlimit, '********'::text as rolpassword, rolvaliduntil, setconfig as rolconfig, pg_authid.oid FROM pg_authid LEFT JOIN pg_db_role_setting s *************** CREATE VIEW pg_shadow AS *** 29,44 **** SELECT rolname AS usename, pg_authid.oid AS usesysid, ! rolcreatedb AS usecreatedb, ! rolsuper AS usesuper, ! rolcatupdate AS usecatupd, ! rolreplication AS userepl, rolpassword AS passwd, rolvaliduntil::abstime AS valuntil, setconfig AS useconfig FROM pg_authid LEFT JOIN pg_db_role_setting s ON (pg_authid.oid = setrole AND setdatabase = 0) ! WHERE rolcanlogin; REVOKE ALL on pg_shadow FROM public; --- 29,44 ---- SELECT rolname AS usename, pg_authid.oid AS usesysid, ! pg_check_role_attribute(pg_authid.oid, 'CREATEDB') AS usecreatedb, ! pg_check_role_attribute(pg_authid.oid, 'SUPERUSER') AS usesuper, ! pg_check_role_attribute(pg_authid.oid, 'CATUPDATE') AS usecatupd, ! pg_check_role_attribute(pg_authid.oid, 'REPLICATION') AS userepl, rolpassword AS passwd, rolvaliduntil::abstime AS valuntil, setconfig AS useconfig FROM pg_authid LEFT JOIN pg_db_role_setting s ON (pg_authid.oid = setrole AND setdatabase = 0) ! WHERE pg_check_role_attribute(pg_authid.oid, 'CANLOGIN'); REVOKE ALL on pg_shadow FROM public; *************** CREATE VIEW pg_group AS *** 48,54 **** oid AS grosysid, ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist FROM pg_authid ! WHERE NOT rolcanlogin; CREATE VIEW pg_user AS SELECT --- 48,54 ---- oid AS grosysid, ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist FROM pg_authid ! WHERE NOT pg_check_role_attribute(pg_authid.oid, 'CANLOGIN'); CREATE VIEW pg_user AS SELECT diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c new file mode 100644 index 1a5244c..c079168 *** a/src/backend/commands/dbcommands.c --- b/src/backend/commands/dbcommands.c *************** static bool get_db_info(const char *name *** 85,91 **** Oid *dbLastSysOidP, TransactionId *dbFrozenXidP, MultiXactId *dbMinMultiP, Oid *dbTablespace, char **dbCollate, char **dbCtype); - static bool have_createdb_privilege(void); static void remove_dbtablespaces(Oid db_id); static bool check_db_file_conflict(Oid db_id); static int errdetail_busy_db(int notherbackends, int npreparedxacts); --- 85,90 ---- *************** createdb(const CreatedbStmt *stmt) *** 291,297 **** * "giveaway" attacks. Note that a superuser will always have both of * these privileges a fortiori. */ ! if (!have_createdb_privilege()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to create database"))); --- 290,296 ---- * "giveaway" attacks. Note that a superuser will always have both of * these privileges a fortiori. */ ! if (!have_role_attribute(ROLE_ATTR_CREATEDB)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to create database"))); *************** RenameDatabase(const char *oldname, cons *** 965,971 **** oldname); /* must have createdb rights */ ! if (!have_createdb_privilege()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to rename database"))); --- 964,970 ---- oldname); /* must have createdb rights */ ! if (!have_role_attribute(ROLE_ATTR_CREATEDB)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to rename database"))); *************** AlterDatabaseOwner(const char *dbname, O *** 1623,1629 **** * databases. Because superusers will always have this right, we need * no special case for them. */ ! if (!have_createdb_privilege()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to change owner of database"))); --- 1622,1628 ---- * databases. Because superusers will always have this right, we need * no special case for them. */ ! if (!have_role_attribute(ROLE_ATTR_CREATEDB)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to change owner of database"))); *************** get_db_info(const char *name, LOCKMODE l *** 1802,1827 **** return result; } - /* Check if current user has createdb privileges */ - static bool - have_createdb_privilege(void) - { - bool result = false; - HeapTuple utup; - - /* Superusers can always do everything */ - if (superuser()) - return true; - - utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(GetUserId())); - if (HeapTupleIsValid(utup)) - { - result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreatedb; - ReleaseSysCache(utup); - } - return result; - } - /* * Remove tablespace directories * --- 1801,1806 ---- diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c new file mode 100644 index 1a73fd8..a4a4d39 *** a/src/backend/commands/user.c --- b/src/backend/commands/user.c *************** static void DelRoleMems(const char *role *** 56,69 **** bool admin_opt); - /* Check if current user has createrole privileges */ - static bool - have_createrole_privilege(void) - { - return has_createrole_privilege(GetUserId()); - } - - /* * CREATE ROLE */ --- 56,61 ---- *************** CreateRole(CreateRoleStmt *stmt) *** 88,93 **** --- 80,86 ---- bool canlogin = false; /* Can this user login? */ bool isreplication = false; /* Is this a replication role? */ bool bypassrls = false; /* Is this a row security enabled role? */ + RoleAttr attributes = ROLE_ATTR_NONE; /* role attributes, initialized to none. */ int connlimit = -1; /* maximum connections allowed */ List *addroleto = NIL; /* roles to make this a member of */ List *rolemembers = NIL; /* roles to be members of this role */ *************** CreateRole(CreateRoleStmt *stmt) *** 249,254 **** --- 242,249 ---- if (dpassword && dpassword->arg) password = strVal(dpassword->arg); + + /* Role Attributes */ if (dissuper) issuper = intVal(dissuper->arg) != 0; if (dinherit) *************** CreateRole(CreateRoleStmt *stmt) *** 261,266 **** --- 256,264 ---- canlogin = intVal(dcanlogin->arg) != 0; if (disreplication) isreplication = intVal(disreplication->arg) != 0; + if (dbypassRLS) + bypassrls = intVal(dbypassRLS->arg) != 0; + if (dconnlimit) { connlimit = intVal(dconnlimit->arg); *************** CreateRole(CreateRoleStmt *stmt) *** 277,284 **** adminmembers = (List *) dadminmembers->arg; if (dvalidUntil) validUntil = strVal(dvalidUntil->arg); - if (dbypassRLS) - bypassrls = intVal(dbypassRLS->arg) != 0; /* Check some permissions first */ if (issuper) --- 275,280 ---- *************** CreateRole(CreateRoleStmt *stmt) *** 304,310 **** } else { ! if (!have_createrole_privilege()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to create role"))); --- 300,306 ---- } else { ! if (!have_role_attribute(ROLE_ATTR_CREATEROLE)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to create role"))); *************** CreateRole(CreateRoleStmt *stmt) *** 355,360 **** --- 351,372 ---- validUntil_datum, validUntil_null); + /* Set all role attributes */ + if (issuper) + attributes |= ROLE_ATTR_SUPERUSER; + if (inherit) + attributes |= ROLE_ATTR_INHERIT; + if (createrole) + attributes |= ROLE_ATTR_CREATEROLE; + if (createdb) + attributes |= ROLE_ATTR_CREATEDB; + if (canlogin) + attributes |= ROLE_ATTR_CANLOGIN; + if (isreplication) + attributes |= ROLE_ATTR_REPLICATION; + if (bypassrls) + attributes |= ROLE_ATTR_BYPASSRLS; + /* * Build a tuple to insert */ *************** CreateRole(CreateRoleStmt *stmt) *** 364,377 **** new_record[Anum_pg_authid_rolname - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->role)); ! new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper); ! new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit); ! new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole); ! new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb); ! /* superuser gets catupdate right by default */ ! new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper); ! new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin); ! new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication); new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit); if (password) --- 376,383 ---- new_record[Anum_pg_authid_rolname - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->role)); ! new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes); ! new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit); if (password) *************** CreateRole(CreateRoleStmt *stmt) *** 394,401 **** new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum; new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null; - new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls); - tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls); /* --- 400,405 ---- *************** AlterRole(AlterRoleStmt *stmt) *** 508,513 **** --- 512,518 ---- DefElem *dvalidUntil = NULL; DefElem *dbypassRLS = NULL; Oid roleid; + RoleAttr attributes; /* Extract options from the statement node tree */ foreach(option, stmt->options) *************** AlterRole(AlterRoleStmt *stmt) *** 661,688 **** * To mess with a superuser you gotta be superuser; else you need * createrole, or just want to change your own password */ ! if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper || issuper >= 0) { if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to alter superusers"))); } ! else if (((Form_pg_authid) GETSTRUCT(tuple))->rolreplication || isreplication >= 0) { if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to alter replication users"))); } ! else if (((Form_pg_authid) GETSTRUCT(tuple))->rolbypassrls || bypassrls >= 0) { if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to change bypassrls attribute"))); } ! else if (!have_createrole_privilege()) { if (!(inherit < 0 && createrole < 0 && --- 666,696 ---- * To mess with a superuser you gotta be superuser; else you need * createrole, or just want to change your own password */ ! ! attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr; ! ! if ((attributes & ROLE_ATTR_SUPERUSER) || issuper >= 0) { if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to alter superusers"))); } ! else if ((attributes & ROLE_ATTR_REPLICATION) || isreplication >= 0) { if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to alter replication users"))); } ! else if ((attributes & ROLE_ATTR_BYPASSRLS) || bypassrls >= 0) { if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to change bypassrls attribute"))); } ! else if (!have_role_attribute(ROLE_ATTR_CREATEROLE)) { if (!(inherit < 0 && createrole < 0 && *************** AlterRole(AlterRoleStmt *stmt) *** 743,785 **** */ if (issuper >= 0) { ! new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0); ! new_record_repl[Anum_pg_authid_rolsuper - 1] = true; ! ! new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0); ! new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true; } if (inherit >= 0) { ! new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0); ! new_record_repl[Anum_pg_authid_rolinherit - 1] = true; } if (createrole >= 0) { ! new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0); ! new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true; } if (createdb >= 0) { ! new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb > 0); ! new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true; } if (canlogin >= 0) { ! new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin > 0); ! new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true; } if (isreplication >= 0) { ! new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication > 0); ! new_record_repl[Anum_pg_authid_rolreplication - 1] = true; } if (dconnlimit) { new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit); --- 751,807 ---- */ if (issuper >= 0) { ! attributes = (issuper > 0) ? attributes | (ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE) ! : attributes & ~(ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE); ! new_record_repl[Anum_pg_authid_rolattr - 1] = true; } if (inherit >= 0) { ! attributes = (inherit > 0) ? attributes | ROLE_ATTR_INHERIT ! : attributes & ~(ROLE_ATTR_INHERIT); ! new_record_repl[Anum_pg_authid_rolattr - 1] = true; } if (createrole >= 0) { ! attributes = (createrole > 0) ? attributes | ROLE_ATTR_CREATEROLE ! : attributes & ~(ROLE_ATTR_CREATEROLE); ! new_record_repl[Anum_pg_authid_rolattr - 1] = true; } if (createdb >= 0) { ! attributes = (createdb > 0) ? attributes | ROLE_ATTR_CREATEDB ! : attributes & ~(ROLE_ATTR_CREATEDB); ! new_record_repl[Anum_pg_authid_rolattr - 1] = true; } if (canlogin >= 0) { ! attributes = (canlogin > 0) ? attributes | ROLE_ATTR_CANLOGIN ! : attributes & ~(ROLE_ATTR_CANLOGIN); ! new_record_repl[Anum_pg_authid_rolattr - 1] = true; } if (isreplication >= 0) { ! attributes = (isreplication > 0) ? attributes | ROLE_ATTR_REPLICATION ! : attributes & ~(ROLE_ATTR_REPLICATION); ! new_record_repl[Anum_pg_authid_rolattr - 1] = true; ! } ! ! if (bypassrls >= 0) ! { ! attributes = (bypassrls > 0) ? attributes | ROLE_ATTR_BYPASSRLS ! : attributes & ~(ROLE_ATTR_BYPASSRLS); ! new_record_repl[Anum_pg_authid_rolattr - 1] = true; } + /* If any role attributes were set, then update. */ + if (new_record_repl[Anum_pg_authid_rolattr - 1]) + new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes); + if (dconnlimit) { new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit); *************** AlterRole(AlterRoleStmt *stmt) *** 815,825 **** new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null; new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true; - if (bypassrls >= 0) - { - new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls > 0); - new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true; - } new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record, new_record_nulls, new_record_repl); --- 837,842 ---- *************** AlterRoleSet(AlterRoleSetStmt *stmt) *** 867,872 **** --- 884,890 ---- HeapTuple roletuple; Oid databaseid = InvalidOid; Oid roleid = InvalidOid; + RoleAttr attributes; if (stmt->role) { *************** AlterRoleSet(AlterRoleSetStmt *stmt) *** 889,895 **** * To mess with a superuser you gotta be superuser; else you need * createrole, or just want to change your own settings */ ! if (((Form_pg_authid) GETSTRUCT(roletuple))->rolsuper) { if (!superuser()) ereport(ERROR, --- 907,914 ---- * To mess with a superuser you gotta be superuser; else you need * createrole, or just want to change your own settings */ ! attributes = ((Form_pg_authid) GETSTRUCT(roletuple))->rolattr; ! if (attributes & ROLE_ATTR_SUPERUSER) { if (!superuser()) ereport(ERROR, *************** AlterRoleSet(AlterRoleSetStmt *stmt) *** 898,904 **** } else { ! if (!have_createrole_privilege() && HeapTupleGetOid(roletuple) != GetUserId()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), --- 917,923 ---- } else { ! if (!have_role_attribute(ROLE_ATTR_CREATEROLE) && HeapTupleGetOid(roletuple) != GetUserId()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), *************** DropRole(DropRoleStmt *stmt) *** 951,957 **** pg_auth_members_rel; ListCell *item; ! if (!have_createrole_privilege()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to drop role"))); --- 970,976 ---- pg_auth_members_rel; ListCell *item; ! if (!have_role_attribute(ROLE_ATTR_CREATEROLE)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to drop role"))); *************** DropRole(DropRoleStmt *stmt) *** 973,978 **** --- 992,998 ---- char *detail_log; SysScanDesc sscan; Oid roleid; + RoleAttr attributes; tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role)); if (!HeapTupleIsValid(tuple)) *************** DropRole(DropRoleStmt *stmt) *** 1013,1020 **** * roles but not superuser roles. This is mainly to avoid the * scenario where you accidentally drop the last superuser. */ ! if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper && ! !superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to drop superusers"))); --- 1033,1040 ---- * roles but not superuser roles. This is mainly to avoid the * scenario where you accidentally drop the last superuser. */ ! attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr; ! if ((attributes & ROLE_ATTR_SUPERUSER) && !superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to drop superusers"))); *************** RenameRole(const char *oldname, const ch *** 1128,1133 **** --- 1148,1154 ---- bool repl_repl[Natts_pg_authid]; int i; Oid roleid; + RoleAttr attributes; rel = heap_open(AuthIdRelationId, RowExclusiveLock); dsc = RelationGetDescr(rel); *************** RenameRole(const char *oldname, const ch *** 1173,1179 **** /* * createrole is enough privilege unless you want to mess with a superuser */ ! if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper) { if (!superuser()) ereport(ERROR, --- 1194,1201 ---- /* * createrole is enough privilege unless you want to mess with a superuser */ ! attributes = ((Form_pg_authid) GETSTRUCT(oldtuple))->rolattr; ! if (attributes & ROLE_ATTR_SUPERUSER) { if (!superuser()) ereport(ERROR, *************** RenameRole(const char *oldname, const ch *** 1182,1188 **** } else { ! if (!have_createrole_privilege()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to rename role"))); --- 1204,1210 ---- } else { ! if (!have_role_attribute(ROLE_ATTR_CREATEROLE)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to rename role"))); *************** AddRoleMems(const char *rolename, Oid ro *** 1409,1415 **** } else { ! if (!have_createrole_privilege() && !is_admin_of_role(grantorId, roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), --- 1431,1437 ---- } else { ! if (!have_role_attribute(ROLE_ATTR_CREATEROLE) && !is_admin_of_role(grantorId, roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), *************** DelRoleMems(const char *rolename, Oid ro *** 1555,1561 **** } else { ! if (!have_createrole_privilege() && !is_admin_of_role(GetUserId(), roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), --- 1577,1583 ---- } else { ! if (!have_role_attribute(ROLE_ATTR_CREATEROLE) && !is_admin_of_role(GetUserId(), roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c new file mode 100644 index 6ce8dae..491dc38 *** a/src/backend/commands/variable.c --- b/src/backend/commands/variable.c *************** check_session_authorization(char **newva *** 776,781 **** --- 776,782 ---- Oid roleid; bool is_superuser; role_auth_extra *myextra; + RoleAttr attributes; /* Do nothing for the boot_val default of NULL */ if (*newval == NULL) *************** check_session_authorization(char **newva *** 800,806 **** } roleid = HeapTupleGetOid(roleTup); ! is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper; ReleaseSysCache(roleTup); --- 801,808 ---- } roleid = HeapTupleGetOid(roleTup); ! attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr; ! is_superuser = (attributes & ROLE_ATTR_SUPERUSER); ReleaseSysCache(roleTup); *************** check_role(char **newval, void **extra, *** 844,849 **** --- 846,852 ---- Oid roleid; bool is_superuser; role_auth_extra *myextra; + RoleAttr attributes; if (strcmp(*newval, "none") == 0) { *************** check_role(char **newval, void **extra, *** 872,878 **** } roleid = HeapTupleGetOid(roleTup); ! is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper; ReleaseSysCache(roleTup); --- 875,882 ---- } roleid = HeapTupleGetOid(roleTup); ! attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr; ! is_superuser = (attributes & ROLE_ATTR_SUPERUSER); ReleaseSysCache(roleTup); diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c new file mode 100644 index 1977f09..5f1126e *** a/src/backend/replication/logical/logicalfuncs.c --- b/src/backend/replication/logical/logicalfuncs.c *************** *** 23,34 **** --- 23,36 ---- #include "access/xlog_internal.h" + #include "catalog/pg_authid.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "mb/pg_wchar.h" + #include "utils/acl.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/inval.h" *************** XLogRead(char *buf, TimeLineID tli, XLog *** 205,211 **** static void check_permissions(void) { ! if (!superuser() && !has_rolreplication(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser or replication role to use replication slots")))); --- 207,213 ---- static void check_permissions(void) { ! if (!have_role_attribute(ROLE_ATTR_REPLICATION)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser or replication role to use replication slots")))); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c new file mode 100644 index bd4701f..bc6a23a *** a/src/backend/replication/slotfuncs.c --- b/src/backend/replication/slotfuncs.c *************** *** 17,32 **** #include "miscadmin.h" #include "access/htup_details.h" #include "replication/slot.h" #include "replication/logical.h" #include "replication/logicalfuncs.h" #include "utils/builtins.h" #include "utils/pg_lsn.h" static void check_permissions(void) { ! if (!superuser() && !has_rolreplication(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser or replication role to use replication slots")))); --- 17,34 ---- #include "miscadmin.h" #include "access/htup_details.h" + #include "catalog/pg_authid.h" #include "replication/slot.h" #include "replication/logical.h" #include "replication/logicalfuncs.h" + #include "utils/acl.h" #include "utils/builtins.h" #include "utils/pg_lsn.h" static void check_permissions(void) { ! if (!have_role_attribute(ROLE_ATTR_REPLICATION)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser or replication role to use replication slots")))); diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c new file mode 100644 index 6c232dc..58633cc *** a/src/backend/rewrite/rowsecurity.c --- b/src/backend/rewrite/rowsecurity.c *************** *** 36,41 **** --- 36,42 ---- #include "access/heapam.h" #include "access/htup_details.h" #include "access/sysattr.h" + #include "catalog/pg_authid.h" #include "catalog/pg_class.h" #include "catalog/pg_inherits_fn.h" #include "catalog/pg_policy.h" *************** check_enable_rls(Oid relid, Oid checkAsU *** 521,527 **** */ if (!checkAsUser && row_security == ROW_SECURITY_OFF) { ! if (has_bypassrls_privilege(user_id)) /* OK to bypass */ return RLS_NONE_ENV; else --- 522,528 ---- */ if (!checkAsUser && row_security == ROW_SECURITY_OFF) { ! if (has_role_attribute(user_id, ROLE_ATTR_BYPASSRLS)) /* OK to bypass */ return RLS_NONE_ENV; else diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c new file mode 100644 index dc6eb2c..88525f4 *** a/src/backend/utils/adt/acl.c --- b/src/backend/utils/adt/acl.c *************** static Oid convert_type_name(text *typen *** 115,120 **** --- 115,121 ---- static AclMode convert_type_priv_string(text *priv_type_text); static AclMode convert_role_priv_string(text *priv_type_text); static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode); + static RoleAttr convert_role_attr_string(text *attr_type_text); static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue); *************** pg_role_aclcheck(Oid role_oid, Oid rolei *** 4602,4607 **** --- 4603,4773 ---- return ACLCHECK_NO_PRIV; } + /* + * pg_has_role_attribute_id + * Check that the role with the given oid has the given named role + * attribute. + * + * Note: This function applies superuser checks. Therefore, if the provided + * role is a superuser, then the result will always be true. + */ + Datum + pg_has_role_attribute_id(PG_FUNCTION_ARGS) + { + Oid roleoid = PG_GETARG_OID(0); + text *attr_type_text = PG_GETARG_TEXT_P(1); + RoleAttr attribute; + + attribute = convert_role_attr_string(attr_type_text); + + PG_RETURN_BOOL(has_role_attribute(roleoid, attribute)); + } + + /* + * pg_has_role_attribute_name + * Check that the named role has the given named role attribute. + * + * Note: This function applies superuser checks. Therefore, if the provided + * role is a superuser, then the result will always be true. + */ + Datum + pg_has_role_attribute_name(PG_FUNCTION_ARGS) + { + Name rolename = PG_GETARG_NAME(0); + text *attr_type_text = PG_GETARG_TEXT_P(1); + Oid roleoid; + RoleAttr attribute; + + roleoid = get_role_oid(NameStr(*rolename), false); + attribute = convert_role_attr_string(attr_type_text); + + PG_RETURN_BOOL(has_role_attribute(roleoid, attribute)); + } + + /* + * pg_check_role_attribute_id + * Check that the role with the given oid has the given named role + * attribute. + * + * Note: This function is different from 'pg_has_role_attribute_id_attr' in that + * it does *not* apply any superuser checks. Therefore, this function will + * always return the set value of the attribute, despite the superuser-ness of + * the provided role. + */ + Datum + pg_check_role_attribute_id(PG_FUNCTION_ARGS) + { + Oid roleoid = PG_GETARG_OID(0); + text *attr_type_text = PG_GETARG_TEXT_P(1); + RoleAttr attribute; + + attribute = convert_role_attr_string(attr_type_text); + + PG_RETURN_BOOL(check_role_attribute(roleoid, attribute)); + } + + /* + * pg_check_role_attribute_name + * Check that the named role has the given named role attribute. + * + * Note: This function is different from 'pg_has_role_attribute_name_attr' in + * that it does *not* apply any superuser checks. Therefore, this function will + * always return the set value of the attribute, despite the superuser-ness of + * the provided role. + */ + Datum + pg_check_role_attribute_name(PG_FUNCTION_ARGS) + { + Name rolename = PG_GETARG_NAME(0); + text *attr_type_text = PG_GETARG_TEXT_P(1); + Oid roleoid; + RoleAttr attribute; + + roleoid = get_role_oid(NameStr(*rolename), false); + attribute = convert_role_attr_string(attr_type_text); + + PG_RETURN_BOOL(check_role_attribute(roleoid, attribute)); + } + + /* + * pg_all_role_attributes + * Convert a RoleAttr representation of role attributes into an array of + * corresponding text values. + * + * The first and only argument is a RoleAttr (int64) representation of the + * role attributes. + */ + Datum + pg_all_role_attributes(PG_FUNCTION_ARGS) + { + RoleAttr attributes = PG_GETARG_INT64(0); + Datum *temp_array; + ArrayType *result; + int i = 0; + + /* + * If no attributes are assigned, then there is no need to go through the + * individual checks for which are assigned. Therefore, we can short circuit + * and return an empty array. + */ + if (attributes == ROLE_ATTR_NONE) + PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID)); + + temp_array = (Datum *) palloc(N_ROLE_ATTRIBUTES * sizeof(Datum)); + + /* Determine which attributes are assigned. */ + if (attributes & ROLE_ATTR_SUPERUSER) + temp_array[i++] = CStringGetTextDatum("Superuser"); + if (attributes & ROLE_ATTR_INHERIT) + temp_array[i++] = CStringGetTextDatum("Inherit"); + if (attributes & ROLE_ATTR_CREATEROLE) + temp_array[i++] = CStringGetTextDatum("Create Role"); + if (attributes & ROLE_ATTR_CREATEDB) + temp_array[i++] = CStringGetTextDatum("Create DB"); + if (attributes & ROLE_ATTR_CATUPDATE) + temp_array[i++] = CStringGetTextDatum("Catalog Update"); + if (attributes & ROLE_ATTR_CANLOGIN) + temp_array[i++] = CStringGetTextDatum("Login"); + if (attributes & ROLE_ATTR_REPLICATION) + temp_array[i++] = CStringGetTextDatum("Replication"); + if (attributes & ROLE_ATTR_BYPASSRLS) + temp_array[i++] = CStringGetTextDatum("Bypass RLS"); + + result = construct_array(temp_array, i, TEXTOID, -1, false, 'i'); + + PG_RETURN_ARRAYTYPE_P(result); + } + + /* + * convert_role_attr_string + * Convert text string to RoleAttr value. + */ + static RoleAttr + convert_role_attr_string(text *attr_type_text) + { + char *attr_type = text_to_cstring(attr_type_text); + + if (pg_strcasecmp(attr_type, "SUPERUSER") == 0) + return ROLE_ATTR_SUPERUSER; + else if (pg_strcasecmp(attr_type, "INHERIT") == 0) + return ROLE_ATTR_INHERIT; + else if (pg_strcasecmp(attr_type, "CREATEROLE") == 0) + return ROLE_ATTR_CREATEROLE; + else if (pg_strcasecmp(attr_type, "CREATEDB") == 0) + return ROLE_ATTR_CREATEDB; + else if (pg_strcasecmp(attr_type, "CATUPDATE") == 0) + return ROLE_ATTR_CATUPDATE; + else if (pg_strcasecmp(attr_type, "CANLOGIN") == 0) + return ROLE_ATTR_CANLOGIN; + else if (pg_strcasecmp(attr_type, "REPLICATION") == 0) + return ROLE_ATTR_REPLICATION; + else if (pg_strcasecmp(attr_type, "BYPASSRLS") == 0) + return ROLE_ATTR_BYPASSRLS; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized role attribute: \"%s\"", attr_type))); + } /* * initialization function (called by InitPostgres) *************** RoleMembershipCacheCallback(Datum arg, i *** 4634,4656 **** } - /* Check if specified role has rolinherit set */ - static bool - has_rolinherit(Oid roleid) - { - bool result = false; - HeapTuple utup; - - utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); - if (HeapTupleIsValid(utup)) - { - result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit; - ReleaseSysCache(utup); - } - return result; - } - - /* * Get a list of roles that the specified roleid has the privileges of * --- 4800,4805 ---- *************** roles_has_privs_of(Oid roleid) *** 4697,4703 **** int i; /* Ignore non-inheriting roles */ ! if (!has_rolinherit(memberid)) continue; /* Find roles that memberid is directly a member of */ --- 4846,4852 ---- int i; /* Ignore non-inheriting roles */ ! if (!has_role_attribute(memberid, ROLE_ATTR_INHERIT)) continue; /* Find roles that memberid is directly a member of */ diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c new file mode 100644 index 2f02303..c0d0718 *** a/src/backend/utils/adt/ri_triggers.c --- b/src/backend/utils/adt/ri_triggers.c *************** *** 33,38 **** --- 33,39 ---- #include "access/htup_details.h" #include "access/sysattr.h" #include "access/xact.h" + #include "catalog/pg_authid.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_operator.h" *************** *** 43,48 **** --- 44,50 ---- #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" *************** RI_Initial_Check(Trigger *trigger, Relat *** 2308,2314 **** * bypassrls right or is the table owner of the table(s) involved which * have RLS enabled. */ ! if (!has_bypassrls_privilege(GetUserId()) && ((pk_rel->rd_rel->relrowsecurity && !pg_class_ownercheck(pkrte->relid, GetUserId())) || (fk_rel->rd_rel->relrowsecurity && --- 2310,2316 ---- * bypassrls right or is the table owner of the table(s) involved which * have RLS enabled. */ ! if (!have_role_attribute(ROLE_ATTR_BYPASSRLS) && ((pk_rel->rd_rel->relrowsecurity && !pg_class_ownercheck(pkrte->relid, GetUserId())) || (fk_rel->rd_rel->relrowsecurity && diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c new file mode 100644 index 8fccb4c..db2a0fb *** a/src/backend/utils/init/miscinit.c --- b/src/backend/utils/init/miscinit.c *************** *** 40,45 **** --- 40,46 ---- #include "storage/pg_shmem.h" #include "storage/proc.h" #include "storage/procarray.h" + #include "utils/acl.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/memutils.h" *************** SetUserIdAndContext(Oid userid, bool sec *** 329,352 **** /* - * Check whether specified role has explicit REPLICATION privilege - */ - bool - has_rolreplication(Oid roleid) - { - bool result = false; - HeapTuple utup; - - utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); - if (HeapTupleIsValid(utup)) - { - result = ((Form_pg_authid) GETSTRUCT(utup))->rolreplication; - ReleaseSysCache(utup); - } - return result; - } - - /* * Initialize user identity during normal backend startup */ void --- 330,335 ---- *************** InitializeSessionUserId(const char *role *** 375,381 **** roleid = HeapTupleGetOid(roleTup); AuthenticatedUserId = roleid; ! AuthenticatedUserIsSuperuser = rform->rolsuper; /* This sets OuterUserId/CurrentUserId too */ SetSessionUserId(roleid, AuthenticatedUserIsSuperuser); --- 358,364 ---- roleid = HeapTupleGetOid(roleTup); AuthenticatedUserId = roleid; ! AuthenticatedUserIsSuperuser = (rform->rolattr & ROLE_ATTR_SUPERUSER); /* This sets OuterUserId/CurrentUserId too */ SetSessionUserId(roleid, AuthenticatedUserIsSuperuser); *************** InitializeSessionUserId(const char *role *** 394,400 **** /* * Is role allowed to login at all? */ ! if (!rform->rolcanlogin) ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("role \"%s\" is not permitted to log in", --- 377,383 ---- /* * Is role allowed to login at all? */ ! if (!(rform->rolattr & ROLE_ATTR_CANLOGIN)) ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("role \"%s\" is not permitted to log in", diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c new file mode 100644 index c348034..268001f *** a/src/backend/utils/init/postinit.c --- b/src/backend/utils/init/postinit.c *************** InitPostgres(const char *in_dbname, Oid *** 762,768 **** { Assert(!bootstrap); ! if (!superuser() && !has_rolreplication(GetUserId())) ereport(FATAL, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser or replication role to start walsender"))); --- 762,768 ---- { Assert(!bootstrap); ! if (!have_role_attribute(ROLE_ATTR_REPLICATION)) ereport(FATAL, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser or replication role to start walsender"))); diff --git a/src/backend/utils/misc/superuser.c b/src/backend/utils/misc/superuser.c new file mode 100644 index ff0f947..9af77ed *** a/src/backend/utils/misc/superuser.c --- b/src/backend/utils/misc/superuser.c *************** *** 22,27 **** --- 22,28 ---- #include "access/htup_details.h" #include "catalog/pg_authid.h" + #include "utils/acl.h" #include "utils/inval.h" #include "utils/syscache.h" #include "miscadmin.h" *************** superuser_arg(Oid roleid) *** 58,63 **** --- 59,65 ---- { bool result; HeapTuple rtup; + RoleAttr attributes; /* Quick out for cache hit */ if (OidIsValid(last_roleid) && last_roleid == roleid) *************** superuser_arg(Oid roleid) *** 71,77 **** rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); if (HeapTupleIsValid(rtup)) { ! result = ((Form_pg_authid) GETSTRUCT(rtup))->rolsuper; ReleaseSysCache(rtup); } else --- 73,80 ---- rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); if (HeapTupleIsValid(rtup)) { ! attributes = ((Form_pg_authid) GETSTRUCT(rtup))->rolattr; ! result = (attributes & ROLE_ATTR_SUPERUSER); ReleaseSysCache(rtup); } else diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c new file mode 100644 index eb633bc..f638167 *** a/src/bin/pg_dump/pg_dumpall.c --- b/src/bin/pg_dump/pg_dumpall.c *************** dumpRoles(PGconn *conn) *** 671,680 **** /* note: rolconfig is dumped later */ if (server_version >= 90500) printfPQExpBuffer(buf, ! "SELECT oid, rolname, rolsuper, rolinherit, " ! "rolcreaterole, rolcreatedb, " ! "rolcanlogin, rolconnlimit, rolpassword, " ! "rolvaliduntil, rolreplication, rolbypassrls, " "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, " "rolname = current_user AS is_current_user " "FROM pg_authid " --- 671,686 ---- /* note: rolconfig is dumped later */ if (server_version >= 90500) printfPQExpBuffer(buf, ! "SELECT oid, rolname, " ! "pg_check_role_attribute(oid, 'SUPERUSER') AS rolsuper, " ! "pg_check_role_attribute(oid, 'INHERIT') AS rolinherit, " ! "pg_check_role_attribute(oid, 'CREATEROLE') AS rolcreaterole, " ! "pg_check_role_attribute(oid, 'CREATEDB') AS rolcreatedb, " ! "pg_check_role_attribute(oid, 'CANLOGIN') AS rolcanlogin, " ! "pg_check_role_attribute(oid, 'REPLICATION') AS rolreplication, " ! "pg_check_role_attribute(oid, 'BYPASSRLS') AS rolbypassrls, " ! "rolconnlimit, rolpassword, " ! "rolvaliduntil, " "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, " "rolname = current_user AS is_current_user " "FROM pg_authid " diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h new file mode 100644 index 3b63d2b..f28d9f4 *** a/src/include/catalog/pg_authid.h --- b/src/include/catalog/pg_authid.h *************** *** 45,60 **** CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO { NameData rolname; /* name of role */ ! bool rolsuper; /* read this field via superuser() only! */ ! bool rolinherit; /* inherit privileges from other roles? */ ! bool rolcreaterole; /* allowed to create more roles? */ ! bool rolcreatedb; /* allowed to create databases? */ ! bool rolcatupdate; /* allowed to alter catalogs manually? */ ! bool rolcanlogin; /* allowed to log in as session user? */ ! bool rolreplication; /* role used for streaming replication */ ! bool rolbypassrls; /* allowed to bypass row level security? */ int32 rolconnlimit; /* max connections allowed (-1=no limit) */ - /* remaining fields may be null; use heap_getattr to read them! */ text rolpassword; /* password, if any */ timestamptz rolvaliduntil; /* password expiration time, if any */ --- 45,52 ---- CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO { NameData rolname; /* name of role */ ! int64 rolattr; /* role attribute bitmask */ int32 rolconnlimit; /* max connections allowed (-1=no limit) */ /* remaining fields may be null; use heap_getattr to read them! */ text rolpassword; /* password, if any */ timestamptz rolvaliduntil; /* password expiration time, if any */ *************** typedef FormData_pg_authid *Form_pg_auth *** 74,101 **** * compiler constants for pg_authid * ---------------- */ ! #define Natts_pg_authid 12 #define Anum_pg_authid_rolname 1 ! #define Anum_pg_authid_rolsuper 2 ! #define Anum_pg_authid_rolinherit 3 ! #define Anum_pg_authid_rolcreaterole 4 ! #define Anum_pg_authid_rolcreatedb 5 ! #define Anum_pg_authid_rolcatupdate 6 ! #define Anum_pg_authid_rolcanlogin 7 ! #define Anum_pg_authid_rolreplication 8 ! #define Anum_pg_authid_rolbypassrls 9 ! #define Anum_pg_authid_rolconnlimit 10 ! #define Anum_pg_authid_rolpassword 11 ! #define Anum_pg_authid_rolvaliduntil 12 /* ---------------- * initial contents of pg_authid * * The uppercase quantities will be replaced at initdb time with * user choices. * ---------------- */ ! DATA(insert OID = 10 ( "POSTGRES" t t t t t t t t -1 _null_ _null_)); #define BOOTSTRAP_SUPERUSERID 10 --- 66,118 ---- * compiler constants for pg_authid * ---------------- */ ! #define Natts_pg_authid 5 #define Anum_pg_authid_rolname 1 ! #define Anum_pg_authid_rolattr 2 ! #define Anum_pg_authid_rolconnlimit 3 ! #define Anum_pg_authid_rolpassword 4 ! #define Anum_pg_authid_rolvaliduntil 5 ! ! /* ---------------- ! * Role attributes are encoded so that we can OR them together in a bitmask. ! * The present representation of RoleAttr (defined in acl.h) limits us to 64 ! * distinct rights. ! * ---------------- ! */ ! #define ROLE_ATTR_SUPERUSER (1<<0) ! #define ROLE_ATTR_INHERIT (1<<1) ! #define ROLE_ATTR_CREATEROLE (1<<2) ! #define ROLE_ATTR_CREATEDB (1<<3) ! #define ROLE_ATTR_CATUPDATE (1<<4) ! #define ROLE_ATTR_CANLOGIN (1<<5) ! #define ROLE_ATTR_REPLICATION (1<<6) ! #define ROLE_ATTR_BYPASSRLS (1<<7) ! #define N_ROLE_ATTRIBUTES 8 /* 1 plus the last 1<<x */ ! #define ROLE_ATTR_NONE 0 ! ! /* ---------------- ! * All currently available attributes. ! * ! * Note: This value is currently used by genbki.pl. Unfortunately, we have to ! * hard code this value as we cannot use an approach like (1 << N_ROLE_ATTRIBUTES) - 1 ! * as genbki.pl simply uses the literal value associated with the #define symbol ! * which causes an incorrect substitution. Therefore, whenever new role attributes ! * are added this value MUST be changed as well. ! * ---------------- ! */ ! #define ROLE_ATTR_ALL 255 /* equals (1 << N_ROLE_ATTRIBUTES) - 1 */ /* ---------------- * initial contents of pg_authid * * The uppercase quantities will be replaced at initdb time with * user choices. + * + * PGROLATTRALL is substituted by genbki.pl to use the value defined by + * ROLE_ATTR_ALL. * ---------------- */ ! DATA(insert OID = 10 ( "POSTGRES" PGROLATTRALL -1 _null_ _null_)); #define BOOTSTRAP_SUPERUSERID 10 diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h new file mode 100644 index deba4b1..ba3b9ec *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** DESCR("rank of hypothetical row without *** 5100,5105 **** --- 5100,5116 ---- DATA(insert OID = 3993 ( dense_rank_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 2 0 20 "2281 2276" "{2281,2276}" "{i,v}" _null_ _null_ hypothetical_dense_rank_final _null_ _null_ _null_ )); DESCR("aggregate final function"); + /* role attribute support functions */ + DATA(insert OID = 3994 ( pg_has_role_attribute PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ pg_has_role_attribute_id _null_ _null_ _null_ )); + DESCR("check role attribute by role oid with superuser bypass check"); + DATA(insert OID = 3995 ( pg_has_role_attribute PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "19 25" _null_ _null_ _null_ _null_ pg_has_role_attribute_name _null_ _null_ _null_ )); + DESCR("check role attribute by role name with superuser bypass check"); + DATA(insert OID = 3996 ( pg_check_role_attribute PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_id _null_ _null_ _null_ )); + DESCR("check role attribute by role id"); + DATA(insert OID = 3997 ( pg_check_role_attribute PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "19 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_name _null_ _null_ _null_ )); + DESCR("check role attribute by role name"); + DATA(insert OID = 3998 ( pg_all_role_attributes PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 1009 "20" _null_ _null_ _null_ _null_ pg_all_role_attributes _null_ _null_ _null_)); + DESCR("convert role attributes to string array"); /* * Symbolic values for provolatile column: these indicate whether the result diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h new file mode 100644 index a8e3164..1687633 *** a/src/include/utils/acl.h --- b/src/include/utils/acl.h *************** typedef enum AclObjectKind *** 200,205 **** --- 200,206 ---- MAX_ACL_KIND /* MUST BE LAST */ } AclObjectKind; + typedef uint64 RoleAttr; /* a bitmask for role attribute bits */ /* * routines used internally *************** extern bool pg_foreign_data_wrapper_owne *** 326,332 **** extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid); extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid); extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid); ! extern bool has_createrole_privilege(Oid roleid); ! extern bool has_bypassrls_privilege(Oid roleid); #endif /* ACL_H */ --- 327,336 ---- extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid); extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid); extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid); ! ! /* role attribute check routines */ ! extern bool has_role_attribute(Oid roleid, RoleAttr attribute); ! extern bool have_role_attribute(RoleAttr attribute); ! extern bool check_role_attribute(Oid roleid, RoleAttr attribute); #endif /* ACL_H */ diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h new file mode 100644 index 565cff3..a32b84c *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** extern Datum pg_has_role_id_name(PG_FUNC *** 106,111 **** --- 106,116 ---- extern Datum pg_has_role_id_id(PG_FUNCTION_ARGS); extern Datum pg_has_role_name(PG_FUNCTION_ARGS); extern Datum pg_has_role_id(PG_FUNCTION_ARGS); + extern Datum pg_has_role_attribute_id(PG_FUNCTION_ARGS); + extern Datum pg_has_role_attribute_name(PG_FUNCTION_ARGS); + extern Datum pg_check_role_attribute_id(PG_FUNCTION_ARGS); + extern Datum pg_check_role_attribute_name(PG_FUNCTION_ARGS); + extern Datum pg_all_role_attributes(PG_FUNCTION_ARGS); /* bool.c */ extern Datum boolin(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out new file mode 100644 index 80c3351..cec0b61 *** a/src/test/regress/expected/rules.out --- b/src/test/regress/expected/rules.out *************** pg_group| SELECT pg_authid.rolname AS gr *** 1314,1320 **** FROM pg_auth_members WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist FROM pg_authid ! WHERE (NOT pg_authid.rolcanlogin); pg_indexes| SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, --- 1314,1320 ---- FROM pg_auth_members WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist FROM pg_authid ! WHERE (NOT pg_check_role_attribute(pg_authid.oid, 'CANLOGIN'::text)); pg_indexes| SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, *************** pg_replication_slots| SELECT l.slot_name *** 1405,1421 **** FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, xmin, catalog_xmin, restart_lsn) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, ! pg_authid.rolsuper, ! pg_authid.rolinherit, ! pg_authid.rolcreaterole, ! pg_authid.rolcreatedb, ! pg_authid.rolcatupdate, ! pg_authid.rolcanlogin, ! pg_authid.rolreplication, pg_authid.rolconnlimit, '********'::text AS rolpassword, pg_authid.rolvaliduntil, - pg_authid.rolbypassrls, s.setconfig AS rolconfig, pg_authid.oid FROM (pg_authid --- 1405,1421 ---- FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, xmin, catalog_xmin, restart_lsn) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, ! pg_check_role_attribute(pg_authid.oid, 'SUPERUSER'::text) AS rolsuper, ! pg_check_role_attribute(pg_authid.oid, 'INHERIT'::text) AS rolinherit, ! pg_check_role_attribute(pg_authid.oid, 'CREATEROLE'::text) AS rolcreaterole, ! pg_check_role_attribute(pg_authid.oid, 'CREATEDB'::text) AS rolcreatedb, ! pg_check_role_attribute(pg_authid.oid, 'CATUPDATE'::text) AS rolcatupdate, ! pg_check_role_attribute(pg_authid.oid, 'CANLOGIN'::text) AS rolcanlogin, ! pg_check_role_attribute(pg_authid.oid, 'REPLICATION'::text) AS rolreplication, ! pg_check_role_attribute(pg_authid.oid, 'BYPASSRLS'::text) AS rolbypassrls, pg_authid.rolconnlimit, '********'::text AS rolpassword, pg_authid.rolvaliduntil, s.setconfig AS rolconfig, pg_authid.oid FROM (pg_authid *************** pg_settings| SELECT a.name, *** 1608,1623 **** FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline); pg_shadow| SELECT pg_authid.rolname AS usename, pg_authid.oid AS usesysid, ! pg_authid.rolcreatedb AS usecreatedb, ! pg_authid.rolsuper AS usesuper, ! pg_authid.rolcatupdate AS usecatupd, ! pg_authid.rolreplication AS userepl, pg_authid.rolpassword AS passwd, (pg_authid.rolvaliduntil)::abstime AS valuntil, s.setconfig AS useconfig FROM (pg_authid LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid)))) ! WHERE pg_authid.rolcanlogin; pg_stat_activity| SELECT s.datid, d.datname, s.pid, --- 1608,1623 ---- FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline); pg_shadow| SELECT pg_authid.rolname AS usename, pg_authid.oid AS usesysid, ! pg_check_role_attribute(pg_authid.oid, 'CREATEDB'::text) AS usecreatedb, ! pg_check_role_attribute(pg_authid.oid, 'SUPERUSER'::text) AS usesuper, ! pg_check_role_attribute(pg_authid.oid, 'CATUPDATE'::text) AS usecatupd, ! pg_check_role_attribute(pg_authid.oid, 'REPLICATION'::text) AS userepl, pg_authid.rolpassword AS passwd, (pg_authid.rolvaliduntil)::abstime AS valuntil, s.setconfig AS useconfig FROM (pg_authid LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid)))) ! WHERE pg_check_role_attribute(pg_authid.oid, 'CANLOGIN'::text); pg_stat_activity| SELECT s.datid, d.datname, s.pid,
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers