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

一、概述

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值