一、概述
AlterRole 在 He3DB 中用来修改已有角色的命令,可以更改角色的权限、密码、有效期限等。
二、AlterRole 命令的执行流程
- PostgresMain
- exec_simple_query →执行简单的 SQL 查询;
- StartTransactionCommand → 开始事务;
- pg_parse_query →解析为内部的抽象语法树(AST);
- PortalRun
- standard_ProcessUtility →权限检查和准备;
- AlterRole→处理创建角色的具体逻辑;
- CommandCounterIncrement→增量更新当前的命令计数器;
- CommitTransactionCommand→ 提交当前事务;
- finish_xact_command→ 在事务结束时,执行必要的清理和关闭操作;
三、核心结构体介绍
(一) AlterRoleStmt 是一个用于定义 He3DB 中 ALTER ROLE 命令语法的数据结构。它是一个结构体,包含了执行 ALTER ROLE 操作所需的所有信息。
typedef struct AlterRoleStmt
{
NodeTag type;
RoleSpec *role; /* role */
List *options; /* List of DefElem nodes */
int action; /* +1 = add members, -1 = drop members */
} AlterRoleStmt;
NodeTag type: 这是一个枚举类型,用于标识该结构体是哪种节点类型。NodeTag 通常用于区分不同的 SQL 语句类型。
RoleSpec *role: 这是一个指向 RoleSpec 结构体的指针,表示要修改的角色。RoleSpec 结构体可能包含角色的名称或其他标识信息。
List *options: 这是一个指向 List 结构的指针,包含了在 ALTER ROLE 语句中指定的各种选项。List 结构通常用于存储一组 DefElem 节点,每个 DefElem 节点代表一个选项(例如,设置密码、设置有效期等)。
int action: 这是一个整数,表示要对角色执行的操作类型。通常,+1 表示向角色中添加成员(例如,将用户添加到角色中),-1 表示从角色中移除成员(例如,将用户从角色中移除)。
四、核心代码解析
在 He3DB 的源代码中, AlterRole(@src\backend\commands\user.c) 用于修改数据库角色属性的命令。你可以使用这一命令来更改角色的密码、权限、登录状态等。
/*
* ALTER ROLE
*
* Note: the rolemembers option accepted here is intended to support the
* backwards-compatible ALTER GROUP syntax. Although it will work to say
* "ALTER ROLE role ROLE rolenames", we don't document it.
*/
Oid
AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
{
Datum new_record[Natts_pg_authid] = {0};
bool new_record_nulls[Natts_pg_authid] = {0};
bool new_record_repl[Natts_pg_authid] = {0};
这三行代码定义了三个数组,用于存储修改后角色的属性值(new_record),存储属性值是否为 NULL 的标志(new_record_nulls),以及标识是否准备替换现有值的标志(new_record_repl)。
Relation pg_authid_rel;
TupleDesc pg_authid_dsc;
HeapTuple tuple,
new_tuple;
Form_pg_authid authform;
这些变量用于数据库操作:pg_authid_rel 为 pg_authid 表的关系,pg_authid_dsc 为表描述,tuple 用于存储当前角色的元组,new_tuple 用于存储修改后的元组,authform 是角色的表单结构。
ListCell *option = NULL;
char *rolename = NULL;
char *password = NULL; /* user password */
int connlimit = -1; /* maximum connections allowed */
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 *drolemembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
这些指针变量用于存储从 stmt 中提取的各个选项(如密码、超级用户权限、继承权限等)。
Oid roleid = 0;
用于存储角色的 OID。
check_rolespec_name(stmt->role,
_("Cannot alter reserved roles."));
检查指定角色名是否为保留角色,若是则报错。
/* Extract options from the statement node tree */
foreach(option, stmt->options)
开始遍历 ALTER ROLE 语句中的选项,提取每一个选项(defel)。
{
DefElem *defel = (DefElem *) lfirst(option);
if (strcmp(defel->defname, "password") == 0) //检查当前选项是否为 “password”。
{
if (dpassword) //如果之前已存在 dpassword 选项,则报错;否则将其保存到 dpassword 变量中。
errorConflictingDefElem(defel, pstate);
dpassword = defel;
}
else if (strcmp(defel->defname, "superuser") == 0) //检查当前选项是否为 “superuser”,并处理与上述类似的逻辑。
{
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, "rolemembers") == 0 &&
stmt->action != 0)
{
if (drolemembers)
errorConflictingDefElem(defel, pstate);
drolemembers = 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 (dconnlimit) //如果用户指定了最大连接数,则将其转换为整数并验证其合法性。
{
connlimit = intVal(dconnlimit->arg);
if (connlimit < -1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid connection limit: %d", connlimit)));
}
if (dvalidUntil) //同样处理有效时间选项。
validUntil = strVal(dvalidUntil->arg);
/*
* Scan the pg_authid relation to be certain the user exists.
*/
pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock); //打开 pg_authid 表并获取排他锁,以确保安全读取。
pg_authid_dsc = RelationGetDescr(pg_authid_rel); //获取表结构描述。
tuple = get_rolespec_tuple(stmt->role); //根据角色名获取角色的元组,并用结构体来访问角色的字段。
authform = (Form_pg_authid) GETSTRUCT(tuple);
rolename = pstrdup(NameStr(authform->rolname));
roleid = authform->oid; //保存角色名和角色的 OID 值。
if (authform->rolsuper || dissuper) //检查是否尝试修改超级用户角色或其属性,若没有超级用户权限则报错。
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to alter superuser roles or change superuser attribute")));
}
else if (authform->rolreplication || disreplication) //检查是否尝试修改具备复制权限的角色,同样检查是否具有相应权限。
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to alter replication roles or change replication attribute")));
}
else if (dbypassRLS) //检查是否尝试修改跳过行级安全性属性。
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to change bypassrls attribute")));
}
else if (!have_createrole_privilege()) //如果没有创建角色的权限,则检查其余选项的修改权限,如果没有权限则报错。
{
/* check the rest */
if (dinherit || dcreaterole || dcreatedb || dcanlogin || dconnlimit ||
drolemembers || dvalidUntil || !dpassword || roleid != GetUserId())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied")));
}
if (dvalidUntil) //如果指定了有效期,则调用函数将其转换为数据格式;否则从现有元组中获取有效时间属性。
{
validUntil_datum = DirectFunctionCall3(timestamptz_in,
CStringGetDatum(validUntil),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
validUntil_null = false;
}
else
{
validUntil_datum = SysCacheGetAttr(AUTHNAME, tuple,
Anum_pg_authid_rolvaliduntil,
&validUntil_null);
}
if (check_password_hook && password)//如果存在密码检查钩子且用户提供了密码,则调用钩子进行密码检查。
(*check_password_hook) (rolename,
password,
get_password_type(password),
validUntil_datum,
validUntil_null);
然后
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
清空 new_record,new_record_nulls 和 new_record_repl 数组,以准备填充新的角色属性。
if (dissuper)
{
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(boolVal(dissuper->arg));
new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
}
如果 dissuper(解除超级用户权限的标志)存在,将 new_record 中对应超级用户权限的位置更新为布尔值,表示此角色是否仍然是超级用户,并标记为需要替换(new_record_repl)。
if (dinherit)
{
new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(boolVal(dinherit->arg));
new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
}
对于 dinherit(角色是否可以继承权限),同样更新 new_record 并标记为需要替换。
if (dcreaterole)
{
new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(boolVal(dcreaterole->arg));
new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true;
}
如果 dcreaterole(角色是否可以创建角色)存在,则更新相应字段并标记。
if (dcreatedb)
{
new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(boolVal(dcreatedb->arg));
new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true;
}
如果 dcreatedb(角色是否可以创建数据库)设置,更新对应字段。
if (dcanlogin)
{
new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(boolVal(dcanlogin->arg));
new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true;
}
如果 dcanlogin(角色是否可以登录)存在,进行类似的处理。
if (disreplication)
{
new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(boolVal(disreplication->arg));
new_record_repl[Anum_pg_authid_rolreplication - 1] = true;
}
对于 disreplication(角色是否可以进行复制操作),同样进行更新。
if (dconnlimit)
{
new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
}
如果有连接限制的设置 dconnlimit,将新的连接限制值(一种整型)更新到新记录。
/* password */
if (password)
{
char *shadow_pass = NULL;
const char *logdetail = NULL;
/* Like in CREATE USER, don't allow an empty password. */
if (password[0] == '\0' ||
plain_crypt_verify(rolename, 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;
}
如果存在 password,定义两个用于处理密码的变量。检查密码是否为空,或者明文密码是否验证通过。如果是,输出警告并将密码字段标记为 NULL。
else
{
/* Encrypt the password to the requested format. */
shadow_pass = encrypt_password(Password_encryption, rolename,
password);
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(shadow_pass);
}
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
}
如果密码有效,则加密密码并存储到新记录中。
/* unset password */
if (dpassword && dpassword->arg == NULL)
{
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
}
如果 dpassword 存在并且其参数为 NULL,标记密码为 NULL,并替换密码字段。
/* valid until */
new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
对角色的有效期进行更新。
if (dbypassRLS)
{
new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(boolVal(dbypassRLS->arg));
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);
CatalogTupleUpdate(pg_authid_rel, &tuple->t_self, new_tuple);
InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
如果 dbypassRLS 表示角色可以绕过行级安全性,那么更新其对应字段。创建一个新的元组(记录),更新数据库中角色的信息。将新元组更新写入到 pg_authid_rel 表中。如果需要,调用对象后置修改钩子。释放元组的系统缓存和占用的内存。
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
*/
if (drolemembers)
{
List *rolemembers = (List *) drolemembers->arg;
CommandCounterIncrement();
if (stmt->action == +1) /* add members to role */
AddRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
GetUserId(), false);
else if (stmt->action == -1) /* drop members from role */
DelRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
false);
}
如果要更改角色成员,首先进行了指令计数的递增,然后根据操作是添加还是删除成员,分别调用 AddRoleMems 或 DelRoleMems 方法来更新角色的成员。
/*
* Close pg_authid, but keep lock till commit.
*/
table_close(pg_authid_rel, NoLock);
return roleid;
}
关闭在 pg_authid 表中的操作,但保持锁直到提交。返回角色的 ID,完成角色更新操作。