一、概述
CreateRole 是 He3DB中的一项功能,用于创建新的数据库角色,并设置其权限和属性。角色可以是用户、组或其他权限实体。创建角色的过程中,系统需要更新多个系统目录,例如 pg_roles 和 pg_authid,以确保角色的存在与权限的正确设置。
二、CreateRole 命令的执行流程
- PostgresMain
- exec_simple_query →执行简单的 SQL 查询;
- StartTransactionCommand → 开始事务;
- pg_parse_query →解析为内部的抽象语法树(AST);
- PortalRun
- standard_ProcessUtility →权限检查和准备;
- CreateRloe(ParseState *pstate, CreateRoleStmt *stmt)→处理创建角色的具体逻辑;
- have_createrole_privilege→检查当前用户是否具有创建角色的权限;
- CommandCounterIncrement→增量更新当前的命令计数器;
- CommitTransactionCommand→ 提交当前事务;
- finish_xact_command→ 在事务结束时,执行必要的清理和关闭操作;
三、核心结构体介绍
(一) CreateRoleStmt 是 He3DB 中用于表示创建角色 (CREATE ROLE) 语句的结构体。它包含了与角色创建相关的各种信息,包括角色类型、角色名称和角色选项。下面是对 CreateRoleStmt 结构体各个成员的详细解释:
typedef struct CreateRoleStmt
{
NodeTag type;
RoleStmtType stmt_type; /* ROLE/USER/GROUP */
char *role; /* role name */
List *options; /* List of DefElem nodes */
} CreateRoleStmt;
-
NodeTag type 这个成员用于标识结构体的类型。在 He3DB 的抽象语法树 (AST) 中,每个节点都有一个类型标识符(NodeTag),用于区分不同类型的节点。CreateRoleStmt 会被标记为其特定的类型,以便在AST中进行正确的处理。
-
RoleStmtType stmt_type:
typedef enum RoleStmtType
{
ROLESTMT_ROLE,//表示一般的角色声明。这通常与创建、修改或删除角色的 SQL 语句相关。
ROLESTMT_USER,//表示用户声明。用户通常是具有登录权限的角色,因此这个类型特指那些允许登录的角色。
ROLESTMT_GROUP //表示组声明。组是多个角色的集合,主要用于简化权限管理,允许将权限授予一组角色。
} RoleStmtType;
-
char *role:这个成员是一个指向字符串的指针,用于存储要创建的角色的名称。例如,如果你要创建一个名为 myuser 的用户,role 就会保存 “myuser”。
-
这个成员是一个指向 List 类型的指针,表示角色创建时可能附带的一系列选项。每个选项都是 DefElem 类型的节点,可能包括诸如 LOGIN、SUPERUSER、PASSWORD 等角色选项。这使得角色的定义更加灵活,可以通过附加的选项来调整角色的属性。
(二) DefElem 是 He3DB 中用来描述 SQL 命令参数的结构体,通常用于定义带有特定选项或参数的命令。以下是对 DefElem 结构体的详细解析,包括它的字段及其含义。
typedef struct DefElem
{
NodeTag type; /* 节点类型标识 */
char *defnamespace; /* 定义的命名空间,NULL 表示没有指定命名空间 */
char *defname; /* 参数名称 */
Node *arg; /* 通常为 Integer、Float、String 或 TypeName 类型,表示参数的值 */
DefElemAction defaction; /* 指定的操作类型(如 SET、ADD、DROP 等) */
int location; /* 位置(token 位置),-1 表示位置未知 */
} DefElem;
四、核心代码解析
在 He3DB 的源代码中,CreateRole(@src\backend\commands\user.c) 函数是处理 CREATE ROLE SQL 语句的主要函数。该函数负责根据传入的 CreateRoleStmt 结构来创建一个新的数据库角色。下面讲分析该函数源码
/*
* CREATE ROLE
*/
Oid
CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
{
Relation pg_authid_rel;
TupleDesc pg_authid_dsc;
HeapTuple tuple;
Datum new_record[Natts_pg_authid] = {0};
bool new_record_nulls[Natts_pg_authid] = {0};
Oid roleid = 0;
ListCell *item = NULL;
ListCell *option = NULL;
char *password = NULL; /* user password */
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? */
bool isreplication = false; /* Is this a replication role? */
bool bypassrls = false; /* Is this a row security enabled role? */
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 */
List *adminmembers = NIL; /* roles to be admins of this role */
char *validUntil = NULL; /* time the login is valid until */
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null = false;
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
DefElem *dcreatedb = NULL;
DefElem *dcanlogin = NULL;
DefElem *disreplication = NULL;
DefElem *dconnlimit = NULL;
DefElem *daddroleto = NULL;
DefElem *drolemembers = NULL;
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = 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)
{
DefElem *defel = (DefElem *) lfirst(option);
if (strcmp(defel->defname, "password") == 0)
{
if (dpassword)
errorConflictingDefElem(defel, pstate);
dpassword = defel;
}
else if (strcmp(defel->defname, "sysid") == 0)
{
ereport(NOTICE,
(errmsg("SYSID can no longer be specified")));
}
else if (strcmp(defel->defname, "superuser") == 0)
{
if (dissuper)
errorConflictingDefElem(defel, pstate);
dissuper = defel;
}
else if (strcmp(defel->defname, "inherit") == 0)
{
if (dinherit)
errorConflictingDefElem(defel, pstate);
dinherit = defel;
}
else if (strcmp(defel->defname, "createrole") == 0)
{
if (dcreaterole)
errorConflictingDefElem(defel, pstate);
dcreaterole = defel;
}
else if (strcmp(defel->defname, "createdb") == 0)
{
if (dcreatedb)
errorConflictingDefElem(defel, pstate);
dcreatedb = defel;
}
else if (strcmp(defel->defname, "canlogin") == 0)
{
if (dcanlogin)
errorConflictingDefElem(defel, pstate);
dcanlogin = defel;
}
else if (strcmp(defel->defname, "isreplication") == 0)
{
if (disreplication)
errorConflictingDefElem(defel, pstate);
disreplication = defel;
}
else if (strcmp(defel->defname, "connectionlimit") == 0)
{
if (dconnlimit)
errorConflictingDefElem(defel, pstate);
dconnlimit = defel;
}
else if (strcmp(defel->defname, "addroleto") == 0)
{
if (daddroleto)
errorConflictingDefElem(defel, pstate);
daddroleto = defel;
}
else if (strcmp(defel->defname, "rolemembers") == 0)
{
if (drolemembers)
errorConflictingDefElem(defel, pstate);
drolemembers = defel;
}
else if (strcmp(defel->defname, "adminmembers") == 0)
{
if (dadminmembers)
errorConflictingDefElem(defel, pstate);
dadminmembers = defel;
}
else if (strcmp(defel->defname, "validUntil") == 0)
{
if (dvalidUntil)
errorConflictingDefElem(defel, pstate);
dvalidUntil = defel;
}
else if (strcmp(defel->defname, "bypassrls") == 0)
{
if (dbypassRLS)
errorConflictingDefElem(defel, pstate);
dbypassRLS = defel;
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
if (dpassword && dpassword->arg)
password = strVal(dpassword->arg);
if (dissuper)
issuper = boolVal(dissuper->arg);
if (dinherit)
inherit = boolVal(dinherit->arg);
if (dcreaterole)
createrole = boolVal(dcreaterole->arg);
if (dcreatedb)
createdb = boolVal(dcreatedb->arg);
if (dcanlogin)
canlogin = boolVal(dcanlogin->arg);
if (disreplication)
isreplication = boolVal(disreplication->arg);
if (dconnlimit)
{
connlimit = intVal(dconnlimit->arg);
if (connlimit < -1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid connection limit: %d", connlimit)));
}
if (daddroleto)
addroleto = (List *) daddroleto->arg;
if (drolemembers)
rolemembers = (List *) drolemembers->arg;
if (dadminmembers)
adminmembers = (List *) dadminmembers->arg;
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
if (dbypassRLS)
bypassrls = boolVal(dbypassRLS->arg);
/* Check some permissions first */
if (issuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create superusers")));
}
else if (isreplication)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create replication users")));
}
else if (bypassrls)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create bypassrls users")));
}
else
{
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create role")));
}
/*
* Check that the user is not trying to create a role in the reserved
* "pg_" namespace.
*/
if (IsReservedName(stmt->role))
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role name \"%s\" is reserved",
stmt->role),
errdetail("Role names starting with \"pg_\" are reserved.")));
/*
* If built with appropriate switch, whine when regression-testing
* conventions for role names are violated.
*/
#ifdef ENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS
if (strncmp(stmt->role, "regress_", 8) != 0)
elog(WARNING, "roles created by regression test cases should have names starting with \"regress_\"");
#endif
/*
* Check the pg_authid relation to be certain the role doesn't already
* exist.
*/
pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock);
pg_authid_dsc = RelationGetDescr(pg_authid_rel);
if (OidIsValid(get_role_oid(stmt->role, true)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("role \"%s\" already exists",
stmt->role)));
/* Convert validuntil to internal form */
if (validUntil)
{
validUntil_datum = DirectFunctionCall3(timestamptz_in,
CStringGetDatum(validUntil),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
validUntil_null = false;
}
else
{
validUntil_datum = (Datum) 0;
validUntil_null = true;
}
/*
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
(*check_password_hook) (stmt->role,
password,
get_password_type(password),
validUntil_datum,
validUntil_null);
/*
* Build a tuple to insert
*/
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
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);
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)
{
char *shadow_pass = NULL;
const char *logdetail = NULL;
/*
* Don't allow an empty password. Libpq treats an empty password the
* same as no password at all, and won't even try to authenticate. But
* other clients might, so allowing it would be confusing. By clearing
* the password when an empty string is specified, the account is
* consistently locked for all clients.
*
* Note that this only covers passwords stored in the database itself.
* There are also checks in the authentication code, to forbid an
* empty password from being used with authentication methods that
* fetch the password from an external system, like LDAP or PAM.
*/
if (password[0] == '\0' ||
plain_crypt_verify(stmt->role, password, "", &logdetail) == STATUS_OK)
{
ereport(NOTICE,
(errmsg("empty string is not a valid password, clearing password")));
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
}
else
{
/* Encrypt the password to the requested format. */
shadow_pass = encrypt_password(Password_encryption, stmt->role,
password);
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(shadow_pass);
}
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
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);
/*
* pg_largeobject_metadata contains pg_authid.oid's, so we use the
* binary-upgrade override.
*/
if (IsBinaryUpgrade)
{
if (!OidIsValid(binary_upgrade_next_pg_authid_oid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("pg_authid OID value not set when in binary upgrade mode")));
roleid = binary_upgrade_next_pg_authid_oid;
binary_upgrade_next_pg_authid_oid = InvalidOid;
}
else
{
roleid = GetNewOidWithIndex(pg_authid_rel, AuthIdOidIndexId,
Anum_pg_authid_oid);
}
new_record[Anum_pg_authid_oid - 1] = ObjectIdGetDatum(roleid);
tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
/*
* Insert new record in the pg_authid table
*/
CatalogTupleInsert(pg_authid_rel, tuple);
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
*/
if (addroleto || adminmembers || rolemembers)
CommandCounterIncrement();
/*
* Add the new role to the specified existing roles.
*/
if (addroleto)
{
RoleSpec *thisrole = makeNode(RoleSpec);
List *thisrole_list = list_make1(thisrole);
List *thisrole_oidlist = list_make1_oid(roleid);
thisrole->roletype = ROLESPEC_CSTRING;
thisrole->rolename = stmt->role;
thisrole->location = -1;
foreach(item, addroleto)
{
RoleSpec *oldrole = lfirst(item);
HeapTuple oldroletup = get_rolespec_tuple(oldrole);
Form_pg_authid oldroleform = (Form_pg_authid) GETSTRUCT(oldroletup);
Oid oldroleid = oldroleform->oid;
char *oldrolename = NameStr(oldroleform->rolname);
AddRoleMems(oldrolename, oldroleid,
thisrole_list,
thisrole_oidlist,
GetUserId(), false);
ReleaseSysCache(oldroletup);
}
}
/*
* Add the specified members to this new role. adminmembers get the admin
* option, rolemembers don't.
*/
AddRoleMems(stmt->role, roleid,
adminmembers, roleSpecsToIds(adminmembers),
GetUserId(), true);
AddRoleMems(stmt->role, roleid,
rolemembers, roleSpecsToIds(rolemembers),
GetUserId(), false);
/* Post creation hook for new role */
InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0);
/*
* Close pg_authid, but keep lock till commit.
*/
table_close(pg_authid_rel, NoLock);
return roleid;
}
根据上述完整代码,大致可分为这几个逻辑模块进行分析:
(一)变量声明与初始化,主要包含角色属性相关的变量、角色成员与选项相关的变量角色选项的定义元素等;
(二)将选项的值转化为相应的内部源码变量;
(三)权限检查;
(四)检查角色是否已经存在、处理角色有效期 (validUntil)、调用密码检查钩子;
(五)根据变量构建一个用于插入新角色的元组(tuple),该元组将被插入到 pg_authid ;
(六) 角色管理;
(1) 变量声明与初始化:
Relation pg_authid_rel;
TupleDesc pg_authid_dsc;
HeapTuple tuple;
Datum new_record[Natts_pg_authid] = {0};
bool new_record_nulls[Natts_pg_authid] = {0};
Oid roleid = 0;
ListCell *item = NULL;
ListCell *option = NULL;
char *password = NULL; /* user password */
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? */
bool isreplication = false; /* Is this a replication role? */
bool bypassrls = false; /* Is this a row security enabled role? */
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 */
List *adminmembers = NIL; /* roles to be admins of this role */
char *validUntil = NULL; /* time the login is valid until */
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null = false;
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
DefElem *dcreatedb = NULL;
DefElem *dcanlogin = NULL;
DefElem *disreplication = NULL;
DefElem *dconnlimit = NULL;
DefElem *daddroleto = NULL;
DefElem *drolemembers = NULL;
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = 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)
{
DefElem *defel = (DefElem *) lfirst(option);
if (strcmp(defel->defname, "password") == 0)
{
if (dpassword)
errorConflictingDefElem(defel, pstate);
dpassword = defel;
}
else if (strcmp(defel->defname, "sysid") == 0)
{
ereport(NOTICE,
(errmsg("SYSID can no longer be specified")));
}
else if (strcmp(defel->defname, "superuser") == 0)
{
if (dissuper)
errorConflictingDefElem(defel, pstate);
dissuper = defel;
}
else if (strcmp(defel->defname, "inherit") == 0)
{
if (dinherit)
errorConflictingDefElem(defel, pstate);
dinherit = defel;
}
else if (strcmp(defel->defname, "createrole") == 0)
{
if (dcreaterole)
errorConflictingDefElem(defel, pstate);
dcreaterole = defel;
}
else if (strcmp(defel->defname, "createdb") == 0)
{
if (dcreatedb)
errorConflictingDefElem(defel, pstate);
dcreatedb = defel;
}
else if (strcmp(defel->defname, "canlogin") == 0)
{
if (dcanlogin)
errorConflictingDefElem(defel, pstate);
dcanlogin = defel;
}
else if (strcmp(defel->defname, "isreplication") == 0)
{
if (disreplication)
errorConflictingDefElem(defel, pstate);
disreplication = defel;
}
else if (strcmp(defel->defname, "connectionlimit") == 0)
{
if (dconnlimit)
errorConflictingDefElem(defel, pstate);
dconnlimit = defel;
}
else if (strcmp(defel->defname, "addroleto") == 0)
{
if (daddroleto)
errorConflictingDefElem(defel, pstate);
daddroleto = defel;
}
else if (strcmp(defel->defname, "rolemembers") == 0)
{
if (drolemembers)
errorConflictingDefElem(defel, pstate);
drolemembers = defel;
}
else if (strcmp(defel->defname, "adminmembers") == 0)
{
if (dadminmembers)
errorConflictingDefElem(defel, pstate);
dadminmembers = defel;
}
else if (strcmp(defel->defname, "validUntil") == 0)
{
if (dvalidUntil)
errorConflictingDefElem(defel, pstate);
dvalidUntil = defel;
}
else if (strcmp(defel->defname, "bypassrls") == 0)
{
if (dbypassRLS)
errorConflictingDefElem(defel, pstate);
dbypassRLS = defel;
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
在上述变量中有如下核心成员:
- Relation pg_authid_rel;表示 pg_authid 系统目录的关系(relation)。这个系统表存储了所有数据库角色的信息。
- Oid roleid;表示角色的唯一标识符(OID)。在创建或修改角色时,分配给该角色的 ID。
- ListCell *item;用于迭代某个列表的指针,可能在处理角色选项或成员时使用。
- char *password = NULL;用户密码,可以在创建用户时设置。
- bool issuper = false;标识角色是否为超级用户(superuser)。
- bool inherit = true;指示角色是否自动继承权限。
- bool createrole = false;指示角色是否有创建其他角色的权限。
- bool createdb = false;指示角色是否有创建数据库的权限。
- bool canlogin = false;指示角色是否能够登录。
- bool isreplication = false; 指示角色是否是用于逻辑复制的角色。
- bool bypassrls = false;指示角色是否能够绕过行安全性 (Row Level Security) 限制。
- int connlimit = -1; 指定角色的最大连接数,-1 表示没有限制。
- List *adminmembers = NIL;指示谁是这个角色的管理员的角色列表。
- char *validUntil = NULL; 登录有效期的截止时间。
(2) 将选项的值转化为相应的内部源码变量;
if (dpassword && dpassword->arg)
password = strVal(dpassword->arg);
if (dissuper)
issuper = boolVal(dissuper->arg);
if (dinherit)
inherit = boolVal(dinherit->arg);
if (dcreaterole)
createrole = boolVal(dcreaterole->arg);
if (dcreatedb)
createdb = boolVal(dcreatedb->arg);
if (dcanlogin)
canlogin = boolVal(dcanlogin->arg);
if (disreplication)
isreplication = boolVal(disreplication->arg);
if (dconnlimit)
{
connlimit = intVal(dconnlimit->arg);
if (connlimit < -1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid connection limit: %d", connlimit)));
}
if (daddroleto)
addroleto = (List *) daddroleto->arg;
if (drolemembers)
rolemembers = (List *) drolemembers->arg;
if (dadminmembers)
adminmembers = (List *) dadminmembers->arg;
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
if (dbypassRLS)
bypassrls = boolVal(dbypassRLS->arg);
这段代码的主要作用是将之前提取的角色选项(如密码、权限等)转换为适当的类型并赋值给相应的变量。通过这种方式,代码确保了在角色声明过程中,不同的选项会被正确解析并存储,便于后续的处理和使用。每个选项都使用布尔值转换函数和字符串转换函数确保类型的安全性和一致性。在此过程调用strVal、boolVal等接口进行数据转化。
(3)检查权限,
/* Check some permissions first */
if (issuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create superusers")));
}
else if (isreplication)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create replication users")));
}
else if (bypassrls)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create bypassrls users")));
}
else
{
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create role")));
}
/*
* Check that the user is not trying to create a role in the reserved
* "pg_" namespace.
*/
if (IsReservedName(stmt->role))
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role name \"%s\" is reserved",
stmt->role),
errdetail("Role names starting with \"pg_\" are reserved.")));
/*
* If built with appropriate switch, whine when regression-testing
* conventions for role names are violated.
*/
这段代码确保在数据库中创建角色时遵循权限管理和名称规则。通过一系列的条件检查:只有超级用户可以创建超级用户、复制用户和具有绕过行级安全的用户。普通用户需要具有创建角色的权限才能创建普通角色。防止使用保留的角色名称以避免潜在的命名冲突,确保数据库对象的管理更加规范。
-
检查超级用户权限 (issuper):如果issuper为真,表示用户正在尝试创建超级用户。调用 superuser() 函数检查当前用户是否具有超级用户权限。如果不是超级用户,则报告错误,提示需要超级用户权限才能创建超级用户。
-
检查复制用户权限 (isreplication):如果 isreplication 为真,表示用户正在尝试创建具有复制权限的用户。同样检查超级用户权限,若当前用户不是超级用户,则报告相应错误。检查绕过行级安全的权限 (bypassrls):
-
如果 bypassrls 为真,表示用户需要创建绕过行级安全控制的用户。依旧检查超级用户权限,如若不具备,则抛出权限不足的错误。
一般角色创建权限:如果以上条件均不满足,即用户正在尝试创建普通角色,则调用 have_createrole_privilege() 检查用户是否具有创建角色的权限。如果没有相应权限,抛出错误,提示权限不足。 -
检查保留名称:通过调用 IsReservedName(stmt->role) 函数来验证所创建的角色名称是否是数据库系统中保留的名称(例如以 pg_ 开头的名称)。如果所提交的角色名称是保留名称,则抛出错误,提示该名称已被保留,无法使用。
(4)检查角色是否已经存在;
pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock);
pg_authid_dsc = RelationGetDescr(pg_authid_rel);
if (OidIsValid(get_role_oid(stmt->role, true)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("role \"%s\" already exists",
stmt->role)));
/* Convert validuntil to internal form */
if (validUntil)
{
validUntil_datum = DirectFunctionCall3(timestamptz_in,
CStringGetDatum(validUntil),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
validUntil_null = false;
}
else
{
validUntil_datum = (Datum) 0;
validUntil_null = true;
}
/*
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
(*check_password_hook) (stmt->role,
password,
get_password_type(password),
validUntil_datum,
validUntil_null);
- 打开pg_authid关系:使用 table_open(AuthIdRelationId, RowExclusiveLock) 函数打开 pg_authid 表,并获取行排他锁(RowExclusiveLock),以保证同时对该表的其他操作进行控制,防止并发冲突。
- 检查角色是否存在:使用 get_role_oid(stmt->role, true) 函数来获取角色的对象标识符(OID)。如果返回值有效(即角色存在),则执行 ereport 报告错误,说明角色已经存在。
- 有效期处理:如果 validUntil 参数有效,使用 DirectFunctionCall3 函数调用内置函数 timestamptz_in 将其转换为 PostgreSQL 使用的内部时间戳格式。CStringGetDatum(validUntil) 将 C 字符串转换为 Datum 类型,第二个参数 InvalidOid 和第三个参数 -1 是用于函数调用的其他参数。validUntil_null 设置为 false,表示此参数包含有效值。
- 处理无效期的情况:如果 validUntil 参数为空,则将 validUntil_datum 设置为 0,并将 validUntil_null 设置为 true,表示该参数为空。
- 密码检查钩子:如果 check_password_hook 已定义且密码参数有效,则调用此钩子函数进行密码的验证。函数参数包括角色名称、密码、密码类型(通过 get_password_type(password) 获取)、有效期数据(validUntil_datum)以及有效期是否为空的标志(validUntil_null)。
(5)根据变量构建一个用于插入新角色的元组(tuple),该元组将被插入到 pg_authid ;
/*
* Build a tuple to insert
*/
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
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);
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)
{
char *shadow_pass = NULL;
const char *logdetail = NULL;
/*
* Don't allow an empty password. Libpq treats an empty password the
* same as no password at all, and won't even try to authenticate. But
* other clients might, so allowing it would be confusing. By clearing
* the password when an empty string is specified, the account is
* consistently locked for all clients.
*
* Note that this only covers passwords stored in the database itself.
* There are also checks in the authentication code, to forbid an
* empty password from being used with authentication methods that
* fetch the password from an external system, like LDAP or PAM.
*/
if (password[0] == '\0' ||
plain_crypt_verify(stmt->role, password, "", &logdetail) == STATUS_OK)
{
ereport(NOTICE,
(errmsg("empty string is not a valid password, clearing password")));
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
}
else
{
/* Encrypt the password to the requested format. */
shadow_pass = encrypt_password(Password_encryption, stmt->role,
password);
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(shadow_pass);
}
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
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);
/*
* pg_largeobject_metadata contains pg_authid.oid's, so we use the
* binary-upgrade override.
*/
if (IsBinaryUpgrade)
{
if (!OidIsValid(binary_upgrade_next_pg_authid_oid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("pg_authid OID value not set when in binary upgrade mode")));
roleid = binary_upgrade_next_pg_authid_oid;
binary_upgrade_next_pg_authid_oid = InvalidOid;
}
else
{
roleid = GetNewOidWithIndex(pg_authid_rel, AuthIdOidIndexId,
Anum_pg_authid_oid);
}
new_record[Anum_pg_authid_oid - 1] = ObjectIdGetDatum(roleid);
tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
/*
* Insert new record in the pg_authid table
*/
CatalogTupleInsert(pg_authid_rel, tuple);
- 初始化内存:MemSet(new_record, 0, sizeof(new_record)); 将 new_record 的内存设置为全零,这为后续的赋值做好准备。MemSet(new_record_nulls, false, sizeof(new_record_nulls)); 同样的操作,但是是将 new_record_nulls 的内存设置为全 false。这个数组通常用来标识哪些字段是 NULL,在这里意味着所有字段都被初始化为有效值。
- 角色名称:使用 DirectFunctionCall1(namein, CStringGetDatum(stmt->role)) 将角色名称字符串转换为 PostgreSQL 内部表示的名称类型,并将其存储在 new_record 的相应位置。Anum_pg_authid_rolname 是系统表 pg_authid 中角色名称列的编号,这里将其索引减去 1 是因为 C 语言的数组索引从 0 开始。
- 角色的其他属性:
issuper: 是否为超级用户。
inherit: 是否继承角色权限。
createrole: 是否允许创建其他角色。
createdb: 是否允许创建数据库。
canlogin: 是否允许登录。
isreplication: 是否为复制角色。
connlimit: 限制该角色的连接数。
每个属性的值通过 BoolGetDatum() 或 Int32GetDatum() 函数进行转换,将 C 类型转换为 PostgreSQL 内部表示的 Datum 类型,便于存储在元组中。 - 密码检查:检查 password 是否不为 NULL,如果为 NULL,则会将密码字段设置为 NULL。
- 禁止空密码:如果 password[0] 是空字符(即空字符串),则触发警告,并清除密码。另外,plain_crypt_verify 函数被用于验证传入的密码是否有效。如果返回 STATUS_OK,则表示密码验证成功,密码会被清空,锁定该用户。如果是空密码,记录警告信息,并将 new_record 中相应的角色密码位置标记为 NULL。
- 密码加密:如果密码不为空,调用 encrypt_password 函数加密密码,然后将加密后的密码存储到新记录数组中。
- 将角色的有效期 (rolvaliduntil) 和相应的空值标记设置在记录中。
- 设置角色的行级安全性(RLS)绕过标志。.
- 处理二进制升级:在二进制升级模式下,检查 binary_upgrade_next_pg_authid_oid 是否有效,不然会报错;如果有效,就将其分配给 roleid,并重置为无效。
- 在非二进制升级模式下,通过 GetNewOidWithIndex 函数从 pg_authid 表中为新角色分配一个新的对象标识符(OID)。
- 最后,利用 heap_form_tuple 将 new_record 和 null 值信息组合成一个新的数据库元组,并通过 CatalogTupleInsert 将其插入到 pg_authid 表中。
(6)角色管理:
/*
* pg_largeobject_metadata contains pg_authid.oid's, so we use the
* binary-upgrade override.
*/
if (IsBinaryUpgrade)
{
if (!OidIsValid(binary_upgrade_next_pg_authid_oid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("pg_authid OID value not set when in binary upgrade mode")));
roleid = binary_upgrade_next_pg_authid_oid;
binary_upgrade_next_pg_authid_oid = InvalidOid;
}
else
{
roleid = GetNewOidWithIndex(pg_authid_rel, AuthIdOidIndexId,
Anum_pg_authid_oid);
}
new_record[Anum_pg_authid_oid - 1] = ObjectIdGetDatum(roleid);
tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
/*
* Insert new record in the pg_authid table
*/
CatalogTupleInsert(pg_authid_rel, tuple);
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
*/
if (addroleto || adminmembers || rolemembers)
CommandCounterIncrement();
/*
* Add the new role to the specified existing roles.
*/
if (addroleto)
{
RoleSpec *thisrole = makeNode(RoleSpec);
List *thisrole_list = list_make1(thisrole);
List *thisrole_oidlist = list_make1_oid(roleid);
thisrole->roletype = ROLESPEC_CSTRING;
thisrole->rolename = stmt->role;
thisrole->location = -1;
foreach(item, addroleto)
{
RoleSpec *oldrole = lfirst(item);
HeapTuple oldroletup = get_rolespec_tuple(oldrole);
Form_pg_authid oldroleform = (Form_pg_authid) GETSTRUCT(oldroletup);
Oid oldroleid = oldroleform->oid;
char *oldrolename = NameStr(oldroleform->rolname);
AddRoleMems(oldrolename, oldroleid,
thisrole_list,
thisrole_oidlist,
GetUserId(), false);
ReleaseSysCache(oldroletup);
}
}
/*
* Add the specified members to this new role. adminmembers get the admin
* option, rolemembers don't.
*/
AddRoleMems(stmt->role, roleid,
adminmembers, roleSpecsToIds(adminmembers),
GetUserId(), true);
AddRoleMems(stmt->role, roleid,
rolemembers, roleSpecsToIds(rolemembers),
GetUserId(), false);
/* Post creation hook for new role */
InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0);
/*
* Close pg_authid, but keep lock till commit.
*/
table_close(pg_authid_rel, NoLock);
return roleid;
- 添加角色到指定现有角色:如果有需要添加的角色(addroleto),则为每个指定的角色创建角色规范并找到其 OID。使用 AddRoleMems 函数将新角色添加到这些现存角色中。
- 添加成员:将指定的成员添加到新角色中。其中,adminmembers 具有管理员选项,而 rolemembers 则没有。调用后创建钩子:
- 调用后创建钩子:对新角色调用创建后的钩子函数 InvokeObjectPostCreateHook。
- 关闭表:关闭 pg_authid 表,但在提交之前保持锁定。
- 返回新角色的 ID:返回新角色的对象 ID(roleid)。
问题1:standard_ProcessUtility权限检查与CreateRloe权限检查有什么区别?
1.standard_ProcessUtility
职责:这是一个通用的用于处理各种数据库操作(如创建、修改、删除等)的函数。它是 PostgreSQL 中的一个高层处理函数,负责分发不同类型的SQL命令和管理执行过程。
权限检查:在处理具体的 SQL 命令之前,该函数会进行一些初步的权限检查。这些检查通常是基于当前用户的权限,以确定他们是否有权执行该操作。例如,这可能涉及确保用户有权限创建角色或更改系统配置。
该函数通常会决定当前用户是否可以继续进行后续的 ProcessUtility 调用。
2. CreateRole
职责:
CreateRole 函数则是专门处理创建角色的逻辑,属于实际执行创建角色操作的具体实现。
权限检查:
在这个函数中,通常会有更详细和具体的权限检查措施,以确保当前用户有足够的权限来创建新角色。这可能涉及检查用户是否拥有相应的角色管理权限(如 CREATE ROLE 权限)。
此外,这里还会处理与角色创建相关的其他约束和判断,例如是否允许默认角色被修改或创建的角色是否有效。
3.总结
作用层级:
standard_ProcessUtility 是一个更高层次的通用函数,负责对不同类型的 SQL 命令进行处理,并执行初步的权限检查。
CreateRole 则是针对角色创建的具体实现,其中包含详细的权限检查和角色创建的具体逻辑。
权限检查内容:
在 standard_ProcessUtility 中的权限检查相对较为宽泛和初步,以确保操作的合法性。在 CreateRole 中,权限检查更为严格和具针对性,因为它需要确保执行创建操作的用户拥有足够的管理权限。
问题2:createrole的DDL中元数据库有什么变动?
-
pg_authid:这个表存储了所有角色的授权标识(Auth ID)信息,包括角色的名称、密码(如果有的话)、权限状态等。当执行 CREATE ROLE 命令时,会在这个表中插入一条新的记录,记录新创建的角色的相关信息。
-
pg_roles:这个视图提供了一个简化的列表,显示了所有角色的基本信息,它是基于 pg_authid 表的,但只显示了部分字段。创建新角色后,pg_roles 视图中也会增加一条记录,反映新角色的信息。
注:执行 CREATE ROLE 命令并不会直接将数据插入到 pg_roles 视图中,因为 pg_roles 是一个视图而不是表。视图本身不存储数据,而是从底层表中获取数据并进行展示。 -
pg_database:如果新创建的角色被指定为某个数据库的拥有者(Owner),那么 pg_database 表中相关数据库的 datdba 字段会被更新为新角色的 OID(对象标识符)。
-
pg_shdepend:这个表记录了共享对象的依赖关系。如果新创建的角色与其他共享对象(如数据库、表空间)有依赖关系,那么相应的记录会被插入到这个表中。
-
pg_depend:这个表记录了对象间的依赖关系。如果新创建的角色与某些对象有依赖关系,那么相应的记录会被插入到这个表中。
-
pg_auth_members:如果新创建的角色被指定为其他角色的成员,那么 pg_auth_members 表中会插入一条记录,表明这种成员关系。