数据库高安全—角色权限:角色创建角色管理

目录

3.1 角色创建

3.2 角色管理

书接上文openGauss安全整体架构&安全认证,从安全整体架构与安全认证两方面,对高斯数据库的高安全性能进行了解读,本篇我们将从角色创建和角色管理两方面对高斯数据库的角色权限进行介绍。

3.1 角色创建

角色是拥有数据库对象和权限的实体,在不同的环境中角色可以认为是一个用户,一个组或者兼顾两者。如果在openGauss上需要创建一个角色,可以使用SQL命令CREATE ROLE,其语法为:

CREATE ROLE role_name [ [ WITH ] option [ ... ] ] [ ENCRYPTED | UNENCRYPTED ] { PASSWORD | IDENTIFIED BY } { 'password' | DISABLE };

创建角色是通过函数CreateRole实现的,其函数接口为:

void CreateRole(CreateRoleStmt* stmt)

其中,CreateRoleStmt为创建角色时所需的数据结构,具体数据结构为:

typedef struct CreateRoleStmt {    NodeTag type;    RoleStmtType stmt_type;  // 将要创建的角色类型 ROLE/USER/GROUP     char* role;              // 角色名    List* options;            // 角色属性列表} CreateRoleStmt;

字段stmt_type是枚举类型,如下所示:

typedef enum RoleStmtType {ROLESTMT_ROLE,    // 代表创建角色ROLESTMT_USER,    // 代表创建用户ROLESTMT_GROUP,  // 代表创建组用户} RoleStmtType;

字段option用来存储角色的属性信息,具体的数据结构为:

typedef struct DefElem {    NodeTag type;    char* defnamespace;      // 节点对应的命名空间    char* defname;          // 节点对应的角色属性名    Node* arg;              // 表示值或类型名    DefElemAction defaction;  // SET/ADD/DROP 等其他未指定的行为} DefElem;

有了上述的关键数据结构,完整的创建角色流程:

图片

图1  openGauss角色创建流程

创建角色时先判断所要创建的角色类型。如果是创建用户,则设置其canlogin属性为true,因为用户默认具有登录权限。而创建角色和创建组时,若角色属性参数没有声明的话,则canlogin属性默认为false。​​​​​​​

// 默认值可能因原始语句类型而异switch (stmt->stmt_type) {case ROLESTMT_ROLE:        break;    case ROLESTMT_USER:         canlogin = true;         break;     case ROLESTMT_GROUP:         break;     default:         break;}

检查完所要创建的角色类型以后,开始循环获取角色属性options中的内容,并将其转换成对应的的角色属性值类型。​​​​​​​

// 从node tree中获取optionforeach (option, stmt->options) {    DefElem* defel = (DefElem*)lfirst(option);
    if (strcmp(defel->defname, "password") == 0 || strcmp(defel->defname, "encryptedPassword") == 0 ||        strcmp(defel->defname, "unencryptedPassword") == 0) {        if (dpassword != NULL) {            clean_role_password(dpassword);            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));        }        dpassword = defel;        if (strcmp(defel->defname, "encryptedPassword") == 0)            encrypt_password = true;        else if (strcmp(defel->defname, "unencryptedPassword") == 0) {            clean_role_password(dpassword);            ereport(ERROR,                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),                    errmsg("Permission denied to create role with option UNENCRYPTED.")));        }    } else if (strcmp(defel->defname, "sysid") == 0) {        ereport(NOTICE, (errmsg("SYSID can no longer be specified")));    } else if (strcmp(defel->defname, "inherit") == 0) {        if (dinherit != NULL) {            clean_role_password(dpassword);            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));        }        dinherit = defel;    } else if (strcmp(defel->defname, "createrole") == 0) {        if (dcreaterole != NULL) {            clean_role_password(dpassword);            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));        }        dcreaterole = defel;    } else if (strcmp(defel->defname, "createdb") == 0) {        if (dcreatedb != NULL) {            clean_role_password(dpassword);            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));        }        dcreatedb = defel;    } else if (strcmp(defel->defname, "useft") == 0) {        if (duseft != NULL) {            clean_role_password(dpassword);            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));        }        duseft = defel;……

根据对应的参数信息转换需要的角色属性值类型,如提取issuper值和createrole值等:​​​​​​​

if (dissuper != NULL)    issuper = intVal(dissuper->arg) != 0;if (dinherit != NULL)    inherit = intVal(dinherit->arg) != 0;if (dcreaterole != NULL)    createrole = intVal(dcreaterole->arg) != 0;if (dcreatedb != NULL)    createdb = intVal(dcreatedb->arg) != 0;……

在完成了转换以后,将角色属性值以及角色的信息一起构建一个pg_authid的元组,再写回系统表并更新索引。​​​​​​​

// 检查pg_authid relation,确认该角色没有存在.Relation pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);    TupleDesc pg_authid_dsc = RelationGetDescr(pg_authid_rel);
    if (OidIsValid(get_role_oid(stmt->role, true))) {        str_reset(password);        ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("role \"%s\" already exists", stmt->role)));}……    // 创建一个插入的tuple    errno_t errorno = memset_s(new_record, sizeof(new_record), 0, sizeof(new_record));    securec_check(errorno, "\0", "\0");    errorno = memset_s(new_record_nulls, sizeof(new_record_nulls), false, sizeof(new_record_nulls));    securec_check(errorno, "\0", "\0");
    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_rolcatupdate - 1] = BoolGetDatum(issuper);    new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);    new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);    new_record[Anum_pg_authid_rolauditadmin - 1] = BoolGetDatum(isauditadmin);    new_record[Anum_pg_authid_rolsystemadmin - 1] = BoolGetDatum(issystemadmin);new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);……    HeapTuple tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
    if (u_sess->proc_cxt.IsBinaryUpgrade && OidIsValid(u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid)) {        HeapTupleSetOid(tuple, u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid);        u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid = InvalidOid;    }
    roleid = simple_heap_insert(pg_authid_rel, tuple);
    if (IsUnderPostmaster) {        if (OidIsValid(rpoid) && (rpoid != DEFAULT_POOL_OID))            recordDependencyOnRespool(AuthIdRelationId, roleid, rpoid);
        u_sess->wlm_cxt->wlmcatalog_update_user = true;}……

完成更新以后,将新创建的角色加入指定存在的父角色中。​​​​​​​

    // 将新角色添加到指定的现有角色中    foreach (item, addroleto) {        char* oldrolename = strVal(lfirst(item));        Oid oldroleid = get_role_oid(oldrolename, false);
        AddRoleMems(            oldrolename, oldroleid, list_make1(makeString(stmt->role)), list_make1_oid(roleid), GetUserId(), false);    }
    AddRoleMems(stmt->role, roleid, adminmembers, roleNamesToIds(adminmembers), GetUserId(), true);    AddRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), GetUserId(), false);

此时,完成整个角色创建的过程。

3.2 角色管理

(1)修改角色属性

如果要修改一个数据库角色,可以使用SQL命令ALTER ROLE。角色属性的修改是通过调用AlterRole函数来实现的,该函数只有一个类型为AlterRoleStmt结构的参数。​​​​​​​

typedef struct AlterRoleStmt {    NodeTag  type;    char*  role; // 角色的名称    List*  options; // 需要修改的属性列表    int  action;  // +1增加成员关系, -1删除成员关系    RoleLockType  lockstatus; // 角色锁定状态} AlterRoleStmt;

修改角色的流程如图2所示。

图片

图2 openGauss角色管理流程图

调用函数AlterRole修改用户角色属性时,首先循环判断options,依次提取要修改的角色属性;然后查看系统表pg_authid判断是否已存在该角色,如果不存在则提示报错;再进行相应的权限判断,检查执行者是否有权限去更改该角色的属性;最后构建一个新的元组,将要更改的属性更新到新元组中,存入系统表pg_authid。同时,AlterRole函数也可以用来调整角色的成员关系,结构体中的action字段值设置为1和-1分别表示增加和删除成员关系,该选项将在授予和回收角色章节具体描述。AlterRole的具体实现如下:​​​​​​​

void AlterRole(AlterRoleStmt* stmt){    . . .    // 循环提取角色的属性options。    foreach (option, stmt->options) {        DefElem* defel = (DefElem*)lfirst(option);
        if (strcmp(defel->defname, "password") == 0 || strcmp(defel->defname, "encryptedPassword") == 0 ||            strcmp(defel->defname, "unencryptedPassword") == 0) {            if (dpassword != NULL) {                clean_role_password(dpassword);                ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));            }            dpassword = defel;            if (strcmp(defel->defname, "encryptedPassword") == 0)                encrypt_password = true;            else if (strcmp(defel->defname, "unencryptedPassword") == 0) {                clean_role_password(dpassword);                ereport(ERROR,                    (errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),                        errmsg("Permission denied to create role with option UNENCRYPTED.")));            }        } else if (strcmp(defel->defname, "createrole") == 0) {            if (dcreaterole != NULL) {                clean_role_password(dpassword);                ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));            }            dcreaterole = defel;        } else if (strcmp(defel->defname, "inherit") == 0) {            if (dinherit != NULL) {                clean_role_password(dpassword);                ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));            }            dinherit = defel;        }. . .        else {            clean_role_password(dpassword);            ereport(ERROR,                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("option \"%s\" not recognized", defel->defname)));        }    }// 将提取的属性赋值给对应的变量。    if (dpassword != NULL && dpassword->arg != NULL) {        head = list_head((List*)dpassword->arg);        if (head != NULL) {            pwdargs = (A_Const*)linitial((List*)dpassword->arg);            if (pwdargs != NULL) {                password = strVal(&pwdargs->val);            }            if (lnext(head)) {                pwdargs = (A_Const*)lsecond((List*)dpassword->arg);                if (pwdargs != NULL) {                    replPasswd = strVal(&pwdargs->val);                }            }        }    }    if (dinherit != NULL)        inherit = intVal(dinherit->arg);    if (dcreaterole != NULL)        createrole = intVal(dcreaterole->arg); . . .    // 查看要修改的角色是否存在,不存在则提示报错。    Relation pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
    HeapTuple tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(stmt->role));    if (!HeapTupleIsValid(tuple)) {        str_reset(password);        str_reset(replPasswd);
        if (!have_createrole_privilege())            ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));        else            ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", stmt->role)));    }roleid = HeapTupleGetOid(tuple);. . .// 检查是否有权限更改相应角色的属性,权限不足则提示报错。    if (roleid == BOOTSTRAP_SUPERUSERID) {        if (!(issuper < 0 && inherit < 0 && createrole < 0 && createdb < 0 && canlogin < 0 && isreplication < 0 &&                isauditadmin < 0 && issystemadmin < 0 && isvcadmin < 0 && useft < 0 && dconnlimit == NULL &&                rolemembers == NULL && validBegin == NULL && validUntil == NULL && drespool == NULL &&                dparent == NULL && dnode_group == NULL && dspacelimit == NULL && dtmpspacelimit == NULL &&                dspillspacelimit == NULL)) {            str_reset(password);            str_reset(replPasswd);            ereport(ERROR,                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),                    errmsg("Permission denied to change privilege of the initial account.")));        }    }    if (dpassword != NULL && roleid == BOOTSTRAP_SUPERUSERID && GetUserId() != BOOTSTRAP_SUPERUSERID) {        str_reset(password);        str_reset(replPasswd);        ereport(ERROR,            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),                errmsg("Permission denied to change password of the initial account.")));    }    . . .    } else if (!have_createrole_privilege()) {        if (!(inherit < 0 && createrole < 0 && createdb < 0 && canlogin < 0 && isreplication < 0 && isauditadmin < 0 &&                issystemadmin < 0 && isvcadmin < 0 && useft < 0 && dconnlimit == NULL && rolemembers == NULL &&                validBegin == NULL && validUntil == NULL && !*respool && !OidIsValid(parentid) && dnode_group == NULL &&                !spacelimit && !tmpspacelimit && !spillspacelimit &&                /* if not superuser or have createrole privilege, permission of lock and unlock is denied */                stmt->lockstatus == DO_NOTHING &&                /* if alter password, it will be handled below */                roleid == GetUserId()) ||            (roleid != GetUserId() && dpassword == NULL)) {            str_reset(password);            str_reset(replPasswd);            ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));        }}...    // 将要更改的角色属性值分别更新到新元组中,再将新元组替代旧元组存入系统表pg_authid中。    if (issuper >= 0) {        new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);        new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
        new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0);        new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true;    }    if (inherit >= 0) {        new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);        new_record_repl[Anum_pg_authid_rolinherit - 1] = true;    }  . . .    HeapTuple new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record, new_record_nulls, new_record_repl);    simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple);
    CatalogUpdateIndexes(pg_authid_rel, new_tuple);   . . .// 判断成员关系,增加或删除成员。    if (stmt->action == 1)         AddRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), GetUserId(), false);    else if (stmt->action == -1) /* drop members from role */        DelRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), false);
    . . .    heap_close(pg_authid_rel, NoLock);}

(2)删除角色

如果要删除一个角色,可以使用SQL命令DROP ROLE。角色的删除是通过调用DropRole函数来实现的,该函数只有一个类型为DropRoleStmt结构的参数。​​​​​​​

typedef struct DropRoleStmt {    NodeTagtype;    List*roles;               // 要删除的角色列表    boolmissing_ok;          // 判断角色是否存在    boolis_user;             // 要删除的是角色还是用户    boolinherit_from_parent;  // 是否继承自父角色    DropBehavior behavior;            // 是否级联删除依赖对象} DropRoleStmt;

删除角色的流程如图3所示。

图片

图3 openGauss角色删除流程图

首先判断当前操作者是否有权限执行该操作,若没有则报错退出;然后检查待删除的角色是否存在,若不存在,则根据missing_ok选择返回ERROR或NOTICE提示信息;再通过扫描系统表pg_authid和pg_auth_members,删除所有涉及待删除角色的元组执行;若behavior取值DROP_CASCADE,则级联删除该角色所拥有的所有数据库对象;最后删除该角色在系统表pg_auth_history和pg_user_status中对应的信息,具体的实现过程如下:​​​​​​​

void DropRole(DropRoleStmt* stmt){    . . .// 检查执行者是否有权限删除角色。    if (!have_createrole_privilege())        ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied to drop role.")));// 循环处理要删除的角色。    foreach (item, stmt->roles) {. . .// 检查要删除的角色是否存在,若不存在则提示报错。        HeapTuple tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role));        if (!HeapTupleIsValid(tuple)) {            if (!stmt->missing_ok) {                ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", role)));            } else {                ereport(NOTICE, (errmsg("role \"%s\" does not exist, skipping", role)));            }            continue;        }        . . .// 当前用户不允许删除。        if (roleid == GetUserId())            ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("current user cannot be dropped")));        if (roleid == GetOuterUserId())            ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("current user cannot be dropped")));        if (roleid == GetSessionUserId())            ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("session user cannot be dropped")));        // 校验执行者和被删除角色的权限,如系统管理员才有权限删除其他系统管理员        if((((Form_pg_authid)GETSTRUCT(tuple))->rolsuper|| ((Form_pg_authid)GETSTRUCT(tuple))->rolsystemadmin) &&            !isRelSuperuser())            ereport(ERROR,(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));        if ((((Form_pg_authid)GETSTRUCT(tuple))->rolauditadmin) &&            g_instance.attr.attr_security.enablePrivilegesSeparate && !isRelSuperuser())            ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));        . . .    // 针对CASCADE的情况,删除该角色拥有的对象。        if (stmt->behavior == DROP_CASCADE) {            char* user = NULL;            CancelQuery(role);            user = (char*)palloc(sizeof(char) * strlen(role) + 1);            errno_t errorno = strncpy_s(user, strlen(role) + 1, role, strlen(role));            securec_check(errorno, "\0", "\0");            drop_objectstmt.behavior = stmt->behavior;            drop_objectstmt.type = T_DropOwnedStmt;            drop_objectstmt.roles = list_make1(makeString(user));
            DropOwnedObjects(&drop_objectstmt);            list_free_deep(drop_objectstmt.roles);        }
        // 检查是否有对象依赖于该角色,若还存在依赖,则提示报错。        if (checkSharedDependencies(AuthIdRelationId, roleid, &detail, &detail_log))            ereport(ERROR,                (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),                    errmsg("role \"%s\" cannot be dropped because some objects depend on it", role),                    errdetail_internal("%s", detail),                    errdetail_log("%s", detail_log)));        // 从相关系统表中删除涉及待删除角色的元组。        simple_heap_delete(pg_authid_rel, &tuple->t_self);. . .        while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan))) {            simple_heap_delete(pg_auth_members_rel, &tmp_tuple->t_self);        }
        systable_endscan(sscan);        DropAuthHistory(roleid);        DropUserStatus(roleid);        DeleteSharedComments(roleid, AuthIdRelationId);        DeleteSharedSecurityLabel(roleid, AuthIdRelationId);        DropSetting(InvalidOid, roleid);. . .    heap_close(pg_auth_members_rel, NoLock);    heap_close(pg_authid_rel, NoLock);}

(3)授予和回收角色

如果要授予或回收角色的成员关系,可以使用SQL命令GRANT/REVOKE。如果声明了WITH ADMIN OPTION选项,那么被加入的成员角色还可以将其他角色加入到父角色中。角色的授予或回收通过调用GrantRole函数来实现,该函数只有一个类型为GrantRoleStmt结构的参数。​​​​​​​

typedef struct GrantRoleStmt {    NodeTag type;    List* granted_roles;// 被授予或回收的角色集合    List* grantee_roles;// 从granted_roles中增加或删除的角色集合    Bool is_grant;// true代表授权,false代表回收    Bool admin_opt;// 是否带有with admin option选项    char* grantor;// 授权者    Drop Behaviorbehavior;// 是否级联回收角色} GrantRoleStmt;

授予角色时,grantee_roles中的角色将被添加到granted_roles,调用函数AddRoleMems实现;回收角色时,将grantee_roles中的角色从granted_roles中删除,调用函数DelRoleMems实现。

AddRoleMems的实现流程如图4所示。

图片

图4 openGauss增加用户成员流程图

函数AddRoleMems的具体实现如下,其中:

  • rolename和roleid分别表示要被加入成员的角色的名称和oid。

  • memberNames和memberIds分别是要添加的角色名称和oid的列表。

  • grantorId表示授权者的oid。

  • admin_opt表示是否带有with admin option选项。

static void AddRoleMems(    const char* rolename, Oid roleid, const List* memberNames, List* memberIds, Oid grantorId, bool admin_opt){. . .    // 校验执行者的权限。    if (superuser_arg(roleid)) {        if (!isRelSuperuser())            ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));    . . .    } . . .    if (grantorId != GetUserId() && !superuser())        ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to set grantor")));// 循环处理要添加的角色。    pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock);    pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);
    forboth(nameitem, memberNames, iditem, memberIds)    {        // 针对角色和成员信息创建pg_auth_members元组,再将新元组插入到系统表中。. . .        new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid);        new_record[Anum_pg_auth_members_member -1] = ObjectIdGetDatum(memberid);        new_record[Anum_pg_auth_members_grantor -  1] = ObjectIdGetDatum(grantorId);        new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);
        if (HeapTupleIsValid(authmem_tuple)) {            new_record_repl[Anum_pg_auth_members_grantor - 1] = true;            new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;            tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc, new_record, new_record_nulls, new_record_repl);            simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple);            CatalogUpdateIndexes(pg_authmem_rel, tuple);            ReleaseSysCache(authmem_tuple);        } else {            tuple = heap_form_tuple(pg_authmem_dsc, new_record, new_record_nulls);            (void)simple_heap_insert(pg_authmem_rel, tuple);            CatalogUpdateIndexes(pg_authmem_rel, tuple);        }    }. . .    heap_close(pg_authmem_rel, NoLock);}

函数DelRoleMems的实现过程类似。首先对执行者的相关权限进行校验,然后循环处理要删除的角色,删除系统表pg_auth_member中相关的元组。

以上内容从角色创建和角色管理两方面对角色权限进行了介绍,下篇将从权限管理和权限检查方面继续解读高斯数据库的角色权限,敬请期待~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/948221.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【U8+】用友U8软件中,出入库流水输出excel的时候提示报表输出引擎错误。

【问题现象】 通过天联高级版客户端登录拥有U8后&#xff0c; 将出入库流水输出excel的时候&#xff0c;提示报表输出引擎错误。 进行报表输出时出现错误&#xff0c;错误信息&#xff1a;找不到“fd6eea8b-fb40-4ce4-8ab4-cddbd9462981.htm”。 如果您正试图从最近使用的文件列…

《GICv3_Software_Overview_Official_Release_B》学习笔记

1.不同版本的 GIC 架构及其主要功能如下图所示&#xff1a; 2.GICv2m&#xff08;Generic Interrupt Controller Virtualization Model&#xff09;是针对ARM架构的GIC&#xff08;通用中断控制器&#xff09;的一种扩展&#xff0c; GICv2m扩展为虚拟化环境中的中断管理提供了…

【循环神经网络】RNN介绍

在人工神经网络中&#xff0c;”浅层网络”是指具有一个输入层、一个输出层和最多一个没有循环连接的隐藏层的网络。随着层数的增加&#xff0c;网络的复杂性也在增加。更多的层或循环连接通常会增加网络的深度&#xff0c;并使其能够提供不同级别的数据表示和特征提取&#xf…

C#调用Lua

目录 xLua导入 打包工具导入 单例基类导入与AB包管理器导入 Lua解析器 文件加载与重定向 Lua解析器管理器 全局变量获取 全局函数获取 对于无参数无返回值 对于有参数有返回值 对于多返回值 对于变长参数 完整代码 List与Dictionary映射Table 类映射Table 接口映射…

麒麟操作系统服务架构保姆级教程(七)Nginx+PHP+Mysql部署服务

上边几篇文章已经交过大家二进制部署nginx和php&#xff0c;现在咱们打通nginx和php&#xff0c;mysql和php&#xff0c;开始部署服务&#xff0c;学会部署服务之后就可以开始学习负载均衡啦&#xff0c;话不多说&#xff0c;咱们直接开始~~~ 目录 一、.nginx部署 二、安装PH…

开源模型迎来颠覆性突破:DeepSeek-V3与Qwen2.5如何重塑AI格局?

不用再纠结选择哪个AI模型了&#xff01;chatTools 一站式提供o1推理模型、GPT4o、Claude和Gemini等多种选择&#xff0c;快来体验吧&#xff01; 在全球人工智能模型快速发展的浪潮中&#xff0c;开源模型正逐渐成为一股不可忽视的力量。近日&#xff0c;DeepSeek-V3和Qwen 2.…

【Java项目】基于SpringBoot的【新生宿舍管理系统】

【Java项目】基于SpringBoot的【新生宿舍管理系统】 技术简介&#xff1a;本系统使用采用B/S架构、Spring Boot框架、MYSQL数据库进行开发设计。 系统简介&#xff1a;管理员登录进入新生宿舍管理系统可以查看首页、个人中心、公告信息管理、院系管理、班级管理、学生管理、宿舍…

《learn_the_architecture_-_trustzone_for_aarch64_102418_0101_03_en》学习笔记

1.TrustZone是Arm A-profile架构中安全架构的名称。TrustZone首次在Armv6K中引入&#xff0c;Armv7-A和Armv8-A也支持。TrustZone提供两个执行环境&#xff0c;它们之间具有系统范围的硬件强制隔离。在Arm架构中&#xff0c;有两种安全状态&#xff1a;安全和非安全。在EL0、EL…

Excel | 空格分隔的行怎么导入excel?

准备工作&#xff1a;windows&#xff0c;一个记事本程序和微软的Excel软件。 打开记事本&#xff0c;选中所有内容&#xff0c;按CtrlA全选&#xff0c;然后复制(CtrlC)。 在Excel中&#xff0c;定位到你想粘贴的单元格&#xff0c;按CtrlV进行粘贴。粘贴后&#xff0c;你会在…

南京观海微电子----GH7009国宇测试盒使用

1. SPI接线 针对7009&#xff1a; 2. 国宇上位机代码准备 在主函数首尾两端加入IO2时序控制的代码、以及国语SPI有效位控制的代码&#xff08;请注意7009和其他700x使用的有效位控制不一致&#xff0c;需要用哪一款加入哪一行即可&#xff09;&#xff1a; 三、国宇SPI读的使…

Arduino Uno简介与使用方法

目录 一、Arduino Uno概述 1. 硬件特性 2. 开发环境 二、Arduino Uno的基本使用方法 1. 硬件连接 2. 软件编程 三、Arduino Uno编程基础 1. 基本语法 2. 常用函数 四、Arduino Uno应用举例 1. LED闪烁 2. 温度检测 3. 超声波测距 五、Arduino Uno的扩展与应用 1…

使用命令行管理git项目

# 初始化一个新的Git仓库 git init # 添加文件到暂存区 git add <file> # 提交暂存区的更改到仓库 git commit -m "commit message" # 查看当前仓库的状态 git status # 查看提交历史 git log # 查看文件的改动 git diff <file> # 创建一个新…

网络安全的学习与实践经验(附资料合集)

学习资源 在线学习平台&#xff1a; Hack This Site&#xff1a;提供从初学者到高级难度的挑战任务&#xff0c;适合练习各种网络安全技术。XCTF_OJ&#xff1a;由XCTF组委会开发的免费在线网络安全网站&#xff0c;提供丰富的培训材料和资源。SecurityTube&#xff1a;提供丰…

基于STM32的热带鱼缸控制系统的设计

文章目录 一、热带鱼缸控制系统1.题目要求2.思路3.电路仿真3.1 未仿真3.2 开始仿真&#xff0c;显示屏显示水温、浑浊度、光照强度等值3.3 当水温低于阈值&#xff0c;开启加热并声光报警3.4 当浑浊度高于阈值&#xff0c;开启自动换水并声光报警3.5 当光照低于阈值&#xff0c…

【DevOps】Jenkins项目发布

Jenkins项目发布 文章目录 Jenkins项目发布前言资源列表基础环境一、Jenkins发布静态网站1.1、项目介绍1.2、部署Web1.3、准备gitlab1.4、配置gitlab1.5、创建项目1.6、推送代码 二、Jenkins中创建gitlab凭据2.1、创建凭据2.2、在Jenkins中添加远程主机2.3、获取gitlab项目的UR…

SSM-SpringMVC

目录 “为什么要学 SpringMVC&#xff1f;它和 Servlet 是什么关系&#xff1f;” “什么是异步&#xff1f;为什么异步交互中常用 JSON 格式&#xff1f;异步请求和 JSON 如何配合&#xff1f;” 一、概述 SpringMVC主要负责 1 SpringMVC的常用组件 2 SpringMVC的工作流程…

基层医联体医院患者历史检验检查数据的快速Python编程分析

​​​​​​​ 一、引言 1.1 研究背景与意义 在当今数字化医疗时代,医疗数据呈爆炸式增长,涵盖患者的基本信息、病史、检验检查结果、治疗方案等各个维度。这些海量且复杂的数据蕴含着巨大价值,为精准医疗决策提供了关键依据。通过对患者历史检验检查数据的深入对比分析…

计算机网络基础(7)中科大郑铨老师笔记

应用层 目标&#xff1a;  网络应用的 原理&#xff1a;网络应用协议的概念和实现方面 传输层的服务模型 客户-服务器模式 对等模式(peerto-peer) 内容分发网络  网络应用的 实例&#xff1a;互联网流行的应用层协 议  HTTP  FTP  SMTP / POP3 / IMAP  DNS…

Fabric环境部署-Git和Node安装

一.安装Git&#xff08;v2.43.0&#xff09; Git 是一个开源的分布式版本管理系统&#xff08;也是全球最大的开源软件存储服务器&#xff09;&#xff0c;用于敏捷高效地处理任何或小或大的项目。搭建区块链需要使用Git&#xff0c;因为区块链的开发和部署需要使用版本控制工…

SAP MM物料管理模块常见BAPI函数清单

【SAP系统研究】 #SAP #MM #物料管理 #函数 #BAPI 1、物料主数据 BAPI_MATERIAL_SAVEDATA 创建/更改物料主数据 BAPI_MATERIAL_SAVEREPLICA 物料主数据视图扩充 BAPI_MATERIAL_EXISTENCECHECK 检查物料主数据是否存在 BAPI_MATERIAL_GETLIST 显示物料主数据明细 BAPI_MATERIALG…