暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

海山数据库(He3DB)源码解读:T_AlterRoleStmt原理浅析

cxp 2024-11-18
35

一、概述

   AlterRole 在 He3DB 中用来修改已有角色的命令,可以更改角色的权限、密码、有效期限等。

二、AlterRole 命令的执行流程

  1. PostgresMain
  2. exec_simple_query →执行简单的 SQL 查询;
  3. StartTransactionCommand → 开始事务;
  4. pg_parse_query →解析为内部的抽象语法树(AST);
  5. PortalRun
  6. standard_ProcessUtility →权限检查和准备;
  7. AlterRole→处理创建角色的具体逻辑;
  8. CommandCounterIncrement→增量更新当前的命令计数器;
  9. CommitTransactionCommand→ 提交当前事务;
  10. finish_xact_command→ 在事务结束时,执行必要的清理和关闭操作;
    在这里插入图片描述
图1 AlterRole 命令的执行流程图

三、核心结构体介绍

 (一)  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,完成角色更新操作。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论