Add a role property 'rolinherit' which, when false, denotes that the role
authorTom Lane
Tue, 26 Jul 2005 16:38:29 +0000 (16:38 +0000)
committerTom Lane
Tue, 26 Jul 2005 16:38:29 +0000 (16:38 +0000)
doesn't automatically inherit the privileges of roles it is a member of;
for such a role, membership in another role can be exploited only by doing
explicit SET ROLE.  The default inherit setting is TRUE, so by default
the behavior doesn't change, but creating a user with NOINHERIT gives closer
adherence to our current reading of SQL99.  Documentation still lacking,
and I think the information schema needs another look.

15 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/func.sgml
src/backend/catalog/aclchk.c
src/backend/catalog/system_views.sql
src/backend/commands/user.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/parser/keywords.c
src/backend/utils/adt/acl.c
src/include/catalog/catversion.h
src/include/catalog/pg_authid.h
src/include/nodes/parsenodes.h
src/include/utils/acl.h
src/test/regress/expected/rules.out

index fa0abca950c7fb5bbb3d74b46731ada0f9e0c04c..0c8ed68195d19ea3ac7ceac3693ea04a98815bab 100644 (file)
@@ -1,6 +1,6 @@
 
 
 
       Role has superuser privileges
      
 
+     
+      rolinherit
+      bool
+      
+      Role automatically inherits privileges of roles it is a
+       member of
+     
+
      
       rolcreaterole
       bool
    that blanks out the password field.
   
 
+  
+   This view explicitly exposes the OID column of the underlying table,
+   since that is needed to do joins to other catalogs.
+  
+
   
    <structname>pg_roles</> Columns
 
       Role has superuser privileges
      
 
+     
+      rolinherit
+      bool
+      
+      Role automatically inherits privileges of roles it is a
+       member of
+     
+
      
       rolcreaterole
       bool
       
       Session defaults for run-time configuration variables
      
+
+     
+      oid
+      oid
+      pg_authid.oid
+      ID of role
+     
     
    
   
index b5ce30105bc595b896289704b02505581b3bdb3a..0fdcb1d0df13fa24e0afb5681907a4bb29548e5c 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -8559,7 +8559,12 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
     can access a role in a particular way.  The possibilities for its
     arguments are analogous to has_table_privilege.
     The desired access privilege type must evaluate to
-    MEMBER.
+    MEMBER or
+    USAGE.
+    MEMBER denotes direct or indirect membership in
+    the role (that is, the right to do SET ROLE), while
+    USAGE denotes whether the privileges of the role
+    are immediately available without doing SET ROLE.
    
 
    
index 8053ca73bbff7a1acff6b55521d4ff80e72e6cc3..9e4b58e1298b31e1cdd7330829cfa3dd26240082 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.115 2005/07/07 20:39:57 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.116 2005/07/26 16:38:26 tgl Exp $
  *
  * NOTES
  *   See acl.h.
@@ -1984,7 +1984,7 @@ pg_class_ownercheck(Oid class_oid, Oid roleid)
 
    ReleaseSysCache(tuple);
 
-   return is_member_of_role(roleid, ownerId);
+   return has_privs_of_role(roleid, ownerId);
 }
 
 /*
@@ -2012,7 +2012,7 @@ pg_type_ownercheck(Oid type_oid, Oid roleid)
 
    ReleaseSysCache(tuple);
 
-   return is_member_of_role(roleid, ownerId);
+   return has_privs_of_role(roleid, ownerId);
 }
 
 /*
@@ -2040,7 +2040,7 @@ pg_oper_ownercheck(Oid oper_oid, Oid roleid)
 
    ReleaseSysCache(tuple);
 
-   return is_member_of_role(roleid, ownerId);
+   return has_privs_of_role(roleid, ownerId);
 }
 
 /*
@@ -2068,7 +2068,7 @@ pg_proc_ownercheck(Oid proc_oid, Oid roleid)
 
    ReleaseSysCache(tuple);
 
-   return is_member_of_role(roleid, ownerId);
+   return has_privs_of_role(roleid, ownerId);
 }
 
 /*
@@ -2096,7 +2096,7 @@ pg_namespace_ownercheck(Oid nsp_oid, Oid roleid)
 
    ReleaseSysCache(tuple);
 
-   return is_member_of_role(roleid, ownerId);
+   return has_privs_of_role(roleid, ownerId);
 }
 
 /*
@@ -2135,7 +2135,7 @@ pg_tablespace_ownercheck(Oid spc_oid, Oid roleid)
    heap_endscan(scan);
    heap_close(pg_tablespace, AccessShareLock);
 
-   return is_member_of_role(roleid, spcowner);
+   return has_privs_of_role(roleid, spcowner);
 }
 
 /*
@@ -2164,7 +2164,7 @@ pg_opclass_ownercheck(Oid opc_oid, Oid roleid)
 
    ReleaseSysCache(tuple);
 
-   return is_member_of_role(roleid, ownerId);
+   return has_privs_of_role(roleid, ownerId);
 }
 
 /*
@@ -2203,7 +2203,7 @@ pg_database_ownercheck(Oid db_oid, Oid roleid)
    heap_endscan(scan);
    heap_close(pg_database, AccessShareLock);
 
-   return is_member_of_role(roleid, dba);
+   return has_privs_of_role(roleid, dba);
 }
 
 /*
@@ -2231,5 +2231,5 @@ pg_conversion_ownercheck(Oid conv_oid, Oid roleid)
 
    ReleaseSysCache(tuple);
 
-   return is_member_of_role(roleid, ownerId);
+   return has_privs_of_role(roleid, ownerId);
 }
index d20a3b6d7f21c673620757d25d816a2bdaf448ae..22e2c9111072877c6d621badb89bdb5ac3952eb8 100644 (file)
@@ -3,20 +3,22 @@
  *
  * Copyright (c) 1996-2005, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.16 2005/06/28 05:08:52 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.17 2005/07/26 16:38:26 tgl Exp $
  */
 
 CREATE VIEW pg_roles AS 
     SELECT 
         rolname,
         rolsuper,
+        rolinherit,
         rolcreaterole,
         rolcreatedb,
         rolcatupdate,
         rolcanlogin,
         '********'::text as rolpassword,
         rolvaliduntil,
-        rolconfig
+        rolconfig,
+        oid
     FROM pg_authid;
 
 CREATE VIEW pg_shadow AS
index 5f8eeae30df436b34395b8db202db813cade540f..493a6bf79044b1aa327c3860d6ad4f7ebe776d80 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.157 2005/07/25 22:12:31 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.158 2005/07/26 16:38:26 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -82,6 +82,7 @@ CreateRole(CreateRoleStmt *stmt)
    bool        encrypt_password = Password_encryption; /* encrypt password? */
    char        encrypted_password[MD5_PASSWD_LEN + 1];
    bool        issuper = false;        /* Make the user a superuser? */
+   bool        inherit = true;         /* Auto inherit privileges? */
    bool        createrole = false;     /* Can this user create roles? */
    bool        createdb = false;       /* Can the user create databases? */
    bool        canlogin = false;       /* Can this user login? */
@@ -91,6 +92,7 @@ CreateRole(CreateRoleStmt *stmt)
    char       *validUntil = NULL;      /* time the login is valid until */
    DefElem    *dpassword = NULL;
    DefElem    *dissuper = NULL;
+   DefElem    *dinherit = NULL;
    DefElem    *dcreaterole = NULL;
    DefElem    *dcreatedb = NULL;
    DefElem    *dcanlogin = NULL;
@@ -99,6 +101,19 @@ CreateRole(CreateRoleStmt *stmt)
    DefElem    *dadminmembers = NULL;
    DefElem    *dvalidUntil = NULL;
 
+   /* The defaults can vary depending on the original statement type */
+   switch (stmt->stmt_type)
+   {
+       case ROLESTMT_ROLE:
+           break;
+       case ROLESTMT_USER:
+           canlogin = true;
+           /* may eventually want inherit to default to false here */
+           break;
+       case ROLESTMT_GROUP:
+           break;
+   }
+
    /* Extract options from the statement node tree */
    foreach(option, stmt->options)
    {
@@ -120,7 +135,7 @@ CreateRole(CreateRoleStmt *stmt)
        }
        else if (strcmp(defel->defname, "sysid") == 0)
        {
-           ereport(WARNING,
+           ereport(NOTICE,
                    (errmsg("SYSID can no longer be specified")));
        }
        else if (strcmp(defel->defname, "superuser") == 0)
@@ -131,6 +146,14 @@ CreateRole(CreateRoleStmt *stmt)
                         errmsg("conflicting or redundant options")));
            dissuper = defel;
        }
+       else if (strcmp(defel->defname, "inherit") == 0)
+       {
+           if (dinherit)
+               ereport(ERROR,
+                       (errcode(ERRCODE_SYNTAX_ERROR),
+                        errmsg("conflicting or redundant options")));
+           dinherit = defel;
+       }
        else if (strcmp(defel->defname, "createrole") == 0)
        {
            if (dcreaterole)
@@ -196,6 +219,8 @@ CreateRole(CreateRoleStmt *stmt)
        password = strVal(dpassword->arg);
    if (dissuper)
        issuper = intVal(dissuper->arg) != 0;
+   if (dinherit)
+       inherit = intVal(dinherit->arg) != 0;
    if (dcreaterole)
        createrole = intVal(dcreaterole->arg) != 0;
    if (dcreatedb)
@@ -261,6 +286,7 @@ CreateRole(CreateRoleStmt *stmt)
        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 */
@@ -367,6 +393,7 @@ AlterRole(AlterRoleStmt *stmt)
    bool        encrypt_password = Password_encryption; /* encrypt password? */
    char        encrypted_password[MD5_PASSWD_LEN + 1];
    int         issuper = -1;           /* Make the user a superuser? */
+   int         inherit = -1;           /* Auto inherit privileges? */
    int         createrole = -1;        /* Can this user create roles? */
    int         createdb = -1;          /* Can the user create databases? */
    int         canlogin = -1;          /* Can this user login? */
@@ -374,6 +401,7 @@ AlterRole(AlterRoleStmt *stmt)
    char       *validUntil = NULL;      /* time the login is valid until */
    DefElem    *dpassword = NULL;
    DefElem    *dissuper = NULL;
+   DefElem    *dinherit = NULL;
    DefElem    *dcreaterole = NULL;
    DefElem    *dcreatedb = NULL;
    DefElem    *dcanlogin = NULL;
@@ -408,6 +436,14 @@ AlterRole(AlterRoleStmt *stmt)
                         errmsg("conflicting or redundant options")));
            dissuper = defel;
        }
+       else if (strcmp(defel->defname, "inherit") == 0)
+       {
+           if (dinherit)
+               ereport(ERROR,
+                       (errcode(ERRCODE_SYNTAX_ERROR),
+                        errmsg("conflicting or redundant options")));
+           dinherit = defel;
+       }
        else if (strcmp(defel->defname, "createrole") == 0)
        {
            if (dcreaterole)
@@ -458,6 +494,8 @@ AlterRole(AlterRoleStmt *stmt)
        password = strVal(dpassword->arg);
    if (dissuper)
        issuper = intVal(dissuper->arg);
+   if (dinherit)
+       inherit = intVal(dinherit->arg);
    if (dcreaterole)
        createrole = intVal(dcreaterole->arg);
    if (dcreatedb)
@@ -497,10 +535,10 @@ AlterRole(AlterRoleStmt *stmt)
                    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                     errmsg("must be superuser to alter superusers")));
    }
-   else
+   else if (!have_createrole_privilege())
    {
-       if (!have_createrole_privilege() &&
-           !(createrole < 0 &&
+       if (!(inherit < 0 &&
+             createrole < 0 &&
              createdb < 0 &&
              canlogin < 0 &&
              !rolemembers &&
@@ -536,6 +574,12 @@ AlterRole(AlterRoleStmt *stmt)
        new_record_repl[Anum_pg_authid_rolcatupdate - 1] = 'r';
    }
 
+   if (inherit >= 0)
+   {
+       new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
+       new_record_repl[Anum_pg_authid_rolinherit - 1] = 'r';
+   }
+
    if (createrole >= 0)
    {
        new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0);
index 8d42cead085d365071faa1035de3f99de53d3b2c..283cf5495144e5f02d0d02a97893d95628bf3afe 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.311 2005/07/02 23:00:39 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.312 2005/07/26 16:38:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2392,6 +2392,7 @@ _copyCreateRoleStmt(CreateRoleStmt *from)
 {
    CreateRoleStmt *newnode = makeNode(CreateRoleStmt);
 
+   COPY_SCALAR_FIELD(stmt_type);
    COPY_STRING_FIELD(role);
    COPY_NODE_FIELD(options);
 
index e8c8a0cbc94c467bb75ab017a2f3b436fe780b7b..47e989dbc7dc12b1c444522362b3e884ee5840c9 100644 (file)
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.248 2005/07/02 23:00:39 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.249 2005/07/26 16:38:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1308,6 +1308,7 @@ _equalDropPLangStmt(DropPLangStmt *a, DropPLangStmt *b)
 static bool
 _equalCreateRoleStmt(CreateRoleStmt *a, CreateRoleStmt *b)
 {
+   COMPARE_SCALAR_FIELD(stmt_type);
    COMPARE_STRING_FIELD(role);
    COMPARE_NODE_FIELD(options);
 
index 3730068915f16b03530cbe733a997fb0b53fc0e0..4d21d4eb9f527459aa166e6084f59f5aead0f44e 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.502 2005/07/25 22:12:32 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.503 2005/07/26 16:38:27 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -361,7 +361,7 @@ static void doNegateFloat(Value *v);
    HANDLER HAVING HEADER HOLD HOUR_P
 
    ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT
-   INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P
+   INDEX INHERIT INHERITS INITIALLY INNER_P INOUT INPUT_P
    INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT
    INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -376,8 +376,8 @@ static void doNegateFloat(Value *v);
    MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
 
    NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB
-   NOCREATEROLE NOCREATEUSER NOLOGIN_P NONE NOSUPERUSER NOT NOTHING NOTIFY
-   NOTNULL NOWAIT NULL_P NULLIF NUMERIC
+   NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
+   NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NUMERIC
 
    OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OR
    ORDER OUT_P OUTER_P OVERLAPS OVERLAY OWNER
@@ -581,6 +581,7 @@ CreateRoleStmt:
            CREATE ROLE RoleId opt_with OptRoleList
                {
                    CreateRoleStmt *n = makeNode(CreateRoleStmt);
+                   n->stmt_type = ROLESTMT_ROLE;
                    n->role = $3;
                    n->options = $5;
                    $$ = (Node *)n;
@@ -630,6 +631,14 @@ OptRoleElem:
                {
                    $$ = makeDefElem("superuser", (Node *)makeInteger(FALSE));
                }
+           | INHERIT
+               {
+                   $$ = makeDefElem("inherit", (Node *)makeInteger(TRUE));
+               }
+           | NOINHERIT
+               {
+                   $$ = makeDefElem("inherit", (Node *)makeInteger(FALSE));
+               }
            | CREATEDB
                {
                    $$ = makeDefElem("createdb", (Node *)makeInteger(TRUE));
@@ -700,10 +709,9 @@ CreateUserStmt:
            CREATE USER RoleId opt_with OptRoleList
                {
                    CreateRoleStmt *n = makeNode(CreateRoleStmt);
+                   n->stmt_type = ROLESTMT_USER;
                    n->role = $3;
-                   n->options = lappend($5,
-                                        makeDefElem("canlogin",
-                                                    (Node *)makeInteger(TRUE)));
+                   n->options = $5;
                    $$ = (Node *)n;
                }
        ;
@@ -829,6 +837,7 @@ CreateGroupStmt:
            CREATE GROUP_P RoleId opt_with OptRoleList
                {
                    CreateRoleStmt *n = makeNode(CreateRoleStmt);
+                   n->stmt_type = ROLESTMT_GROUP;
                    n->role = $3;
                    n->options = $5;
                    $$ = (Node *)n;
@@ -7996,6 +8005,7 @@ unreserved_keyword:
            | INCLUDING
            | INCREMENT
            | INDEX
+           | INHERIT
            | INHERITS
            | INPUT_P
            | INSENSITIVE
@@ -8028,6 +8038,7 @@ unreserved_keyword:
            | NOCREATEDB
            | NOCREATEROLE
            | NOCREATEUSER
+           | NOINHERIT
            | NOLOGIN_P
            | NOSUPERUSER
            | NOTHING
index 726e7fc01e36744d89b721f5a696f29d066d08c8..5d4cab2124fffabda770cfb5daeb2b8006f748d3 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.162 2005/06/29 20:34:14 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.163 2005/07/26 16:38:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -165,6 +165,7 @@ static const ScanKeyword ScanKeywords[] = {
    {"including", INCLUDING},
    {"increment", INCREMENT},
    {"index", INDEX},
+   {"inherit", INHERIT},
    {"inherits", INHERITS},
    {"initially", INITIALLY},
    {"inner", INNER_P},
@@ -219,6 +220,7 @@ static const ScanKeyword ScanKeywords[] = {
    {"nocreatedb", NOCREATEDB},
    {"nocreaterole", NOCREATEROLE},
    {"nocreateuser", NOCREATEUSER},
+   {"noinherit", NOINHERIT},
    {"nologin", NOLOGIN_P},
    {"none", NONE},
    {"nosuperuser", NOSUPERUSER},
index 7517f2743f9c43753ec2432370757c871bd6d7c6..48a24db182635d3b8ef1f7803eae55203172796c 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.121 2005/07/26 00:04:18 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.122 2005/07/26 16:38:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  * all the roles the "given role" is a member of, directly or indirectly.
  * The cache is flushed whenever we detect a change in pg_auth_members.
  *
+ * There are actually two caches, one computed under "has_privs" rules
+ * (do not recurse where rolinherit isn't true) and one computed under
+ * "is_member" rules (recurse regardless of rolinherit).
+ *
  * Possibly this mechanism should be generalized to allow caching membership
- * info for more than one role?
+ * info for multiple roles?
+ *
+ * The has_privs cache is:
+ * cached_privs_role is the role OID the cache is for.
+ * cached_privs_roles is an OID list of roles that cached_privs_role
+ *     has the privileges of (always including itself).
+ * The cache is valid if cached_privs_role is not InvalidOid.
  *
- * cached_role is the role OID the cache is for.
- * cached_memberships is an OID list of roles that cached_role is a member of.
- * The cache is valid if cached_role is not InvalidOid.
+ * The is_member cache is similarly:
+ * cached_member_role is the role OID the cache is for.
+ * cached_membership_roles is an OID list of roles that cached_member_role
+ *     is a member of (always including itself).
+ * The cache is valid if cached_member_role is not InvalidOid.
  */
-static Oid cached_role = InvalidOid;
-static List    *cached_memberships = NIL;
+static Oid cached_privs_role = InvalidOid;
+static List    *cached_privs_roles = NIL;
+static Oid cached_member_role = InvalidOid;
+static List    *cached_membership_roles = NIL;
 
 
 static const char *getid(const char *s, char *n);
@@ -999,7 +1013,7 @@ aclmask(const Acl *acl, Oid roleid, Oid ownerId,
    result = 0;
 
    /* Owner always implicitly has all grant options */
-   if (is_member_of_role(roleid, ownerId))
+   if (has_privs_of_role(roleid, ownerId))
    {
        result = mask & ACLITEM_ALL_GOPTION_BITS;
        if (result == mask)
@@ -1042,7 +1056,7 @@ aclmask(const Acl *acl, Oid roleid, Oid ownerId,
            continue;           /* already checked it */
 
        if ((aidata->ai_privs & remaining) &&
-           is_member_of_role(roleid, aidata->ai_grantee))
+           has_privs_of_role(roleid, aidata->ai_grantee))
        {
            result |= aidata->ai_privs & mask;
            if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
@@ -2653,8 +2667,10 @@ pg_has_role_id_id(PG_FUNCTION_ARGS)
  * convert_role_priv_string
  *     Convert text string to AclMode value.
  *
- * There is only one interesting option, MEMBER, which we represent by
- * ACL_USAGE since no formal ACL bit is defined for it.  This convention
+ * We use USAGE to denote whether the privileges of the role are accessible
+ * (has_privs), MEMBER to denote is_member, and MEMBER WITH GRANT OPTION
+ * (or ADMIN OPTION) to denote is_admin.  There is no ACL bit corresponding
+ * to MEMBER so we cheat and use ACL_CREATE for that.  This convention
  * is shared only with pg_role_aclcheck, below.
  */
 static AclMode
@@ -2668,12 +2684,15 @@ convert_role_priv_string(text *priv_type_text)
    /*
     * Return mode from priv_type string
     */
-   if (pg_strcasecmp(priv_type, "MEMBER") == 0)
+   if (pg_strcasecmp(priv_type, "USAGE") == 0)
        return ACL_USAGE;
-   if (pg_strcasecmp(priv_type, "MEMBER WITH GRANT OPTION") == 0)
-       return ACL_GRANT_OPTION_FOR(ACL_USAGE);
-   if (pg_strcasecmp(priv_type, "MEMBER WITH ADMIN OPTION") == 0)
-       return ACL_GRANT_OPTION_FOR(ACL_USAGE);
+   if (pg_strcasecmp(priv_type, "MEMBER") == 0)
+       return ACL_CREATE;
+   if (pg_strcasecmp(priv_type, "USAGE WITH GRANT OPTION") == 0 ||
+       pg_strcasecmp(priv_type, "USAGE WITH ADMIN OPTION") == 0 ||
+       pg_strcasecmp(priv_type, "MEMBER WITH GRANT OPTION") == 0 ||
+       pg_strcasecmp(priv_type, "MEMBER WITH ADMIN OPTION") == 0)
+       return ACL_GRANT_OPTION_FOR(ACL_CREATE);
 
    ereport(ERROR,
            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -2688,20 +2707,22 @@ convert_role_priv_string(text *priv_type_text)
 static AclResult
 pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode)
 {
-   if (mode & ACL_GRANT_OPTION_FOR(ACL_USAGE))
+   if (mode & ACL_GRANT_OPTION_FOR(ACL_CREATE))
    {
        if (is_admin_of_role(roleid, role_oid))
            return ACLCHECK_OK;
-       else
-           return ACLCHECK_NO_PRIV;
    }
-   else
+   if (mode & ACL_CREATE)
    {
        if (is_member_of_role(roleid, role_oid))
            return ACLCHECK_OK;
-       else
-           return ACLCHECK_NO_PRIV;
    }
+   if (mode & ACL_USAGE)
+   {
+       if (has_privs_of_role(roleid, role_oid))
+           return ACLCHECK_OK;
+   }
+   return ACLCHECK_NO_PRIV;
 }
 
 
@@ -2730,14 +2751,130 @@ initialize_acl(void)
 static void
 RoleMembershipCacheCallback(Datum arg, Oid relid)
 {
-   /* Force membership cache to be recomputed on next use */
-   cached_role = InvalidOid;
+   /* Force membership caches to be recomputed on next use */
+   cached_privs_role = InvalidOid;
+   cached_member_role = InvalidOid;
+}
+
+
+/* Check if specified role has rolinherit set */
+static bool
+has_rolinherit(Oid roleid)
+{
+   bool        result = false;
+   HeapTuple   utup;
+
+   utup = SearchSysCache(AUTHOID,
+                         ObjectIdGetDatum(roleid),
+                         0, 0, 0);
+   if (HeapTupleIsValid(utup))
+   {
+       result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
+       ReleaseSysCache(utup);
+   }
+   return result;
+}
+
+
+/*
+ * Does member have the privileges of role (directly or indirectly)?
+ *
+ * This is defined not to recurse through roles that don't have rolinherit
+ * set; for such roles, membership implies the ability to do SET ROLE, but
+ * the privileges are not available until you've done so.
+ *
+ * Since indirect membership testing is relatively expensive, we cache
+ * a list of memberships.
+ */
+bool
+has_privs_of_role(Oid member, Oid role)
+{
+   List        *roles_list;
+   ListCell    *l;
+   List        *new_cached_privs_roles;
+   MemoryContext   oldctx;
+
+   /* Fast path for simple case */
+   if (member == role)
+       return true;
+
+   /* Superusers have every privilege, so are part of every role */
+   if (superuser_arg(member))
+       return true;
+
+   /* If cache is already valid, just use the list */
+   if (OidIsValid(cached_privs_role) && cached_privs_role == member)
+       return list_member_oid(cached_privs_roles, role);
+
+   /* 
+    * Find all the roles that member is a member of,
+    * including multi-level recursion.  The role itself will always
+    * be the first element of the resulting list.
+    *
+    * Each element of the list is scanned to see if it adds any indirect
+    * memberships.  We can use a single list as both the record of
+    * already-found memberships and the agenda of roles yet to be scanned.
+    * This is a bit tricky but works because the foreach() macro doesn't
+    * fetch the next list element until the bottom of the loop.
+    */
+   roles_list = list_make1_oid(member);
+
+   foreach(l, roles_list)
+   {
+       Oid     memberid = lfirst_oid(l);
+       CatCList    *memlist;
+       int         i;
+
+       /* Ignore non-inheriting roles */
+       if (!has_rolinherit(memberid))
+           continue;
+
+       /* Find roles that memberid is directly a member of */
+       memlist = SearchSysCacheList(AUTHMEMMEMROLE, 1,
+                                    ObjectIdGetDatum(memberid),
+                                    0, 0, 0);
+       for (i = 0; i < memlist->n_members; i++)
+       {
+           HeapTuple   tup = &memlist->members[i]->tuple;
+           Oid     otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
+
+           /*
+            * Even though there shouldn't be any loops in the membership
+            * graph, we must test for having already seen this role.
+            * It is legal for instance to have both A->B and A->C->B.
+            */
+           if (!list_member_oid(roles_list, otherid))
+               roles_list = lappend_oid(roles_list, otherid);
+       }
+       ReleaseSysCacheList(memlist);
+   }
+
+   /*
+    * Copy the completed list into TopMemoryContext so it will persist.
+    */
+   oldctx = MemoryContextSwitchTo(TopMemoryContext);
+   new_cached_privs_roles = list_copy(roles_list);
+   MemoryContextSwitchTo(oldctx);
+   list_free(roles_list);
+
+   /*
+    * Now safe to assign to state variable
+    */
+   cached_privs_role = InvalidOid; /* just paranoia */
+   list_free(cached_privs_roles);
+   cached_privs_roles = new_cached_privs_roles;
+   cached_privs_role = member;
+
+   /* And now we can return the answer */
+   return list_member_oid(cached_privs_roles, role);
 }
 
 
 /*
  * Is member a member of role (directly or indirectly)?
  *
+ * This is defined to recurse through roles regardless of rolinherit.
+ *
  * Since indirect membership testing is relatively expensive, we cache
  * a list of memberships.
  */
@@ -2746,7 +2883,7 @@ is_member_of_role(Oid member, Oid role)
 {
    List        *roles_list;
    ListCell    *l;
-   List        *new_cached_memberships;
+   List        *new_cached_membership_roles;
    MemoryContext   oldctx;
 
    /* Fast path for simple case */
@@ -2758,8 +2895,8 @@ is_member_of_role(Oid member, Oid role)
        return true;
 
    /* If cache is already valid, just use the list */
-   if (OidIsValid(cached_role) && cached_role == member)
-       return list_member_oid(cached_memberships, role);
+   if (OidIsValid(cached_member_role) && cached_member_role == member)
+       return list_member_oid(cached_membership_roles, role);
 
    /* 
     * Find all the roles that member is a member of,
@@ -2804,20 +2941,20 @@ is_member_of_role(Oid member, Oid role)
     * Copy the completed list into TopMemoryContext so it will persist.
     */
    oldctx = MemoryContextSwitchTo(TopMemoryContext);
-   new_cached_memberships = list_copy(roles_list);
+   new_cached_membership_roles = list_copy(roles_list);
    MemoryContextSwitchTo(oldctx);
    list_free(roles_list);
 
    /*
     * Now safe to assign to state variable
     */
-   cached_role = InvalidOid;   /* just paranoia */
-   list_free(cached_memberships);
-   cached_memberships = new_cached_memberships;
-   cached_role = member;
+   cached_member_role = InvalidOid;    /* just paranoia */
+   list_free(cached_membership_roles);
+   cached_membership_roles = new_cached_membership_roles;
+   cached_member_role = member;
 
    /* And now we can return the answer */
-   return list_member_oid(cached_memberships, role);
+   return list_member_oid(cached_membership_roles, role);
 }
 
 /*
index 592ea17b115856d5e27006c20105d95bf11d40b9..38f31b114bf0f4a4ebfc54e57ec71347c5af549a 100644 (file)
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.291 2005/07/26 00:04:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.292 2005/07/26 16:38:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 200507251
+#define CATALOG_VERSION_NO 200507261
 
 #endif
index 2ea15fea8a1a7cb8702a00585bc8aabf731a90aa..6672138d865da0c7e1ad490524a35593f7194e9a 100644 (file)
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_authid.h,v 1.1 2005/06/28 05:09:05 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_authid.h,v 1.2 2005/07/26 16:38:28 tgl Exp $
  *
  * NOTES
  *   the genbki.sh script reads this file and generates .bki
@@ -44,6 +44,7 @@ CATALOG(pg_authid,1260) BKI_SHARED_RELATION
 {
    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? */
@@ -69,16 +70,17 @@ typedef FormData_pg_authid *Form_pg_authid;
  *     compiler constants for pg_authid
  * ----------------
  */
-#define Natts_pg_authid                    9
+#define Natts_pg_authid                    10
 #define Anum_pg_authid_rolname         1
 #define Anum_pg_authid_rolsuper            2
-#define Anum_pg_authid_rolcreaterole   3
-#define Anum_pg_authid_rolcreatedb     4
-#define Anum_pg_authid_rolcatupdate        5
-#define Anum_pg_authid_rolcanlogin     6
-#define Anum_pg_authid_rolpassword     7
-#define Anum_pg_authid_rolvaliduntil   8
-#define Anum_pg_authid_rolconfig       9
+#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_rolpassword     8
+#define Anum_pg_authid_rolvaliduntil   9
+#define Anum_pg_authid_rolconfig       10
 
 /* ----------------
  *     initial contents of pg_authid
@@ -87,7 +89,7 @@ typedef FormData_pg_authid *Form_pg_authid;
  * user choices.
  * ----------------
  */
-DATA(insert OID = 10 ( "POSTGRES" t t t t t _null_ _null_ _null_ ));
+DATA(insert OID = 10 ( "POSTGRES" t t t t t _null_ _null_ _null_ ));
 
 #define BOOTSTRAP_SUPERUSERID 10
 
index 32f9b03c585a243dd43651389f3ef86bc8299d3f..6d388b07d3192387e805b188a2199d50cd10891c 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.285 2005/06/28 19:51:24 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.286 2005/07/26 16:38:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1139,11 +1139,24 @@ typedef struct DropPLangStmt
 
 /* ----------------------
  * Create/Alter/Drop Role Statements
+ *
+ * Note: these node types are also used for the backwards-compatible
+ * Create/Alter/Drop User/Group statements.  In the ALTER and DROP cases
+ * there's really no need to distinguish what the original spelling was,
+ * but for CREATE we mark the type because the defaults vary.
  * ----------------------
  */
+typedef enum RoleStmtType
+{
+   ROLESTMT_ROLE,
+   ROLESTMT_USER,
+   ROLESTMT_GROUP
+} RoleStmtType;
+
 typedef struct CreateRoleStmt
 {
    NodeTag     type;
+   RoleStmtType stmt_type;     /* ROLE/USER/GROUP */
    char       *role;           /* role name */
    List       *options;        /* List of DefElem nodes */
 } CreateRoleStmt;
index d3ef0031985aa427fb111992e1016d81f5b28d17..1f216009098fdfd30b14e2d9bf7afa618a60c19a 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.82 2005/07/14 21:46:30 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.83 2005/07/26 16:38:29 tgl Exp $
  *
  * NOTES
  *   An ACL array is simply an array of AclItems, representing the union
@@ -210,6 +210,7 @@ extern AclMode aclmask(const Acl *acl, Oid roleid, Oid ownerId,
        AclMode mask, AclMaskHow how);
 extern int aclmembers(const Acl *acl, Oid **roleids);
 
+extern bool has_privs_of_role(Oid member, Oid role);
 extern bool is_member_of_role(Oid member, Oid role);
 extern bool is_admin_of_role(Oid member, Oid role);
 extern void check_is_member_of_role(Oid member, Oid role);
index 41cc113b6dd7006584a752fc87fa9fa806899cd4..e204864c3ce93f820d5c2710ccfda9ab92116a94 100644 (file)
@@ -1281,7 +1281,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
  pg_indexes               | SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, t.spcname AS "tablespace", pg_get_indexdef(i.oid) AS indexdef FROM ((((pg_index x JOIN pg_class c ON ((c.oid = x.indrelid))) JOIN pg_class i ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace))) WHERE ((c.relkind = 'r'::"char") AND (i.relkind = 'i'::"char"));
  pg_locks                 | SELECT l.locktype, l."database", l.relation, l.page, l.tuple, l.transactionid, l.classid, l.objid, l.objsubid, l."transaction", l.pid, l."mode", l."granted" FROM pg_lock_status() l(locktype text, "database" oid, relation oid, page integer, tuple smallint, transactionid xid, classid oid, objid oid, objsubid smallint, "transaction" xid, pid integer, "mode" text, "granted" boolean);
  pg_prepared_xacts        | SELECT p."transaction", p.gid, p."prepared", u.rolname AS "owner", d.datname AS "database" FROM ((pg_prepared_xact() p("transaction" xid, gid text, "prepared" timestamp with time zone, ownerid oid, dbid oid) LEFT JOIN pg_authid u ON ((p.ownerid = u.oid))) LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
- pg_roles                 | SELECT pg_authid.rolname, pg_authid.rolsuper, pg_authid.rolcreaterole, pg_authid.rolcreatedb, pg_authid.rolcatupdate, pg_authid.rolcanlogin, '********'::text AS rolpassword, pg_authid.rolvaliduntil, pg_authid.rolconfig FROM pg_authid;
+ pg_roles                 | SELECT pg_authid.rolname, pg_authid.rolsuper, pg_authid.rolinherit, pg_authid.rolcreaterole, pg_authid.rolcreatedb, pg_authid.rolcatupdate, pg_authid.rolcanlogin, '********'::text AS rolpassword, pg_authid.rolvaliduntil, pg_authid.rolconfig, pg_authid.oid FROM pg_authid;
  pg_rules                 | SELECT n.nspname AS schemaname, c.relname AS tablename, r.rulename, pg_get_ruledef(r.oid) AS definition FROM ((pg_rewrite r JOIN pg_class c ON ((c.oid = r.ev_class))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (r.rulename <> '_RETURN'::name);
  pg_settings              | SELECT a.name, a.setting, a.category, a.short_desc, a.extra_desc, a.context, a.vartype, a.source, a.min_val, a.max_val FROM pg_show_all_settings() a(name text, setting text, category text, short_desc text, extra_desc text, context text, vartype text, source text, min_val text, max_val text);
  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.rolpassword AS passwd, (pg_authid.rolvaliduntil)::abstime AS valuntil, pg_authid.rolconfig AS useconfig FROM pg_authid WHERE pg_authid.rolcanlogin;