Browse Source

rbac part1

Signed-off-by: shanghaikid <jiangruiyi@gmail.com>
shanghaikid 1 year ago
parent
commit
c8f0a94362

+ 40 - 17
client/src/http/User.ts

@@ -17,50 +17,73 @@ export class UserHttp extends BaseModel {
     Object.assign(this, props);
     Object.assign(this, props);
   }
   }
 
 
-  static USER_URL = `/users`;
+  static USERS_URL = `/users`;
+  static ROLES_URL = `/users/roles`;
 
 
+  // get user data
   static getUsers() {
   static getUsers() {
-    return super.search({ path: this.USER_URL, params: {} });
+    return super.search({ path: this.USERS_URL, params: {} });
   }
   }
 
 
+  // create user
   static createUser(data: CreateUserParams) {
   static createUser(data: CreateUserParams) {
-    return super.create({ path: this.USER_URL, data });
+    return super.create({ path: this.USERS_URL, data });
   }
   }
 
 
+  // update user (pass)
   static updateUser(data: UpdateUserParams) {
   static updateUser(data: UpdateUserParams) {
-    return super.update({ path: this.USER_URL, data });
+    return super.update({ path: this.USERS_URL, data });
   }
   }
 
 
+  // delete user
   static deleteUser(data: DeleteUserParams) {
   static deleteUser(data: DeleteUserParams) {
-    return super.delete({ path: `${this.USER_URL}/${data.username}` });
+    return super.delete({ path: `${this.USERS_URL}/${data.username}` });
   }
   }
 
 
-  static createRole(data: CreateRoleParams) {
-    return super.create({ path: `${this.USER_URL}/roles`, data });
+  // update user role
+  static updateUserRole(data: AssignRoleParams) {
+    return super.update({
+      path: `${this.USERS_URL}/${data.username}/role/update`,
+      data,
+    });
   }
   }
 
 
-  static getRoles() {
-    return super.search({ path: `${this.USER_URL}/roles`, params: {} });
+  // unassign user role
+  static unassignUserRole(data: UnassignRoleParams) {
+    return super.update({
+      path: `${this.USERS_URL}/${data.username}/role/unassign`,
+      data,
+    });
   }
   }
 
 
+  // create a role
+  static createRole(data: CreateRoleParams) {
+    return super.create({ path: `${this.ROLES_URL}`, data });
+  }
+
+  // delete a role
   static deleteRole(data: DeleteRoleParams) {
   static deleteRole(data: DeleteRoleParams) {
-    return super.delete({ path: `${this.USER_URL}/roles/${data.roleName}` });
+    return super.delete({ path: `${this.ROLES_URL}/${data.roleName}` });
   }
   }
 
 
-  static updateUserRole(data: AssignRoleParams) {
-    return super.update({
-      path: `${this.USER_URL}/${data.username}/role/update`,
-      data,
-    });
+  // get all roles
+  static getRoles() {
+    return super.search({ path: `${this.ROLES_URL}`, params: {} });
   }
   }
 
 
-  static unassignUserRole(data: UnassignRoleParams) {
+  // update role privileges
+  static updateRolePrivileges(data: CreateRoleParams) {
     return super.update({
     return super.update({
-      path: `${this.USER_URL}/${data.username}/role/unassign`,
+      path: `${this.ROLES_URL}/${data.roleName}/updatePrivileges`,
       data,
       data,
     });
     });
   }
   }
 
 
+  // get RBAC info
+  static getRBAC() {
+    return super.search({ path: `${this.USERS_URL}/rbac`, params: {} });
+  }
+
   get _names() {
   get _names() {
     return this.names;
     return this.names;
   }
   }

+ 9 - 0
client/src/i18n/en/user.ts

@@ -12,12 +12,21 @@ const userTrans = {
   isNotSame: 'Not same as new password',
   isNotSame: 'Not same as new password',
   deleteTip:
   deleteTip:
     'Please select at least one item to drop and the root user can not be dropped.',
     'Please select at least one item to drop and the root user can not be dropped.',
+
+  // role
   deleteEditRoleTip: 'root role is not editable.',
   deleteEditRoleTip: 'root role is not editable.',
   role: 'Role',
   role: 'Role',
   editRole: 'Edit Role',
   editRole: 'Edit Role',
   roles: 'Roles',
   roles: 'Roles',
   createRoleTitle: 'Create Role',
   createRoleTitle: 'Create Role',
   updateRoleSuccess: 'User Role',
   updateRoleSuccess: 'User Role',
+  type: 'Type',
+
+  // Privileges
+  privileges: 'Privileges',
+  objectCollection: 'Collection',
+  objectGlobal: 'Global',
+  objectUser: 'User',
 };
 };
 
 
 export default userTrans;
 export default userTrans;

+ 0 - 86
client/src/pages/user/CreateRole.tsx

@@ -1,86 +0,0 @@
-import { makeStyles, Theme } from '@material-ui/core';
-import { FC, useMemo, useState } from 'react';
-import { useTranslation } from 'react-i18next';
-import DialogTemplate from '@/components/customDialog/DialogTemplate';
-import CustomInput from '@/components/customInput/CustomInput';
-import { ITextfieldConfig } from '@/components/customInput/Types';
-import { useFormValidation } from '@/hooks/Form';
-import { formatForm } from '@/utils/Form';
-import { CreateRoleProps, CreateRoleParams } from './Types';
-
-const useStyles = makeStyles((theme: Theme) => ({
-  input: {
-    margin: theme.spacing(3, 0, 0.5),
-  },
-}));
-
-const CreateRole: FC<CreateRoleProps> = ({ handleCreate, handleClose }) => {
-  const { t: commonTrans } = useTranslation();
-  const { t: userTrans } = useTranslation('user');
-  const { t: btnTrans } = useTranslation('btn');
-  const { t: warningTrans } = useTranslation('warning');
-  const attuTrans = commonTrans('attu');
-
-  const [form, setForm] = useState<CreateRoleParams>({
-    roleName: '',
-  });
-  const checkedForm = useMemo(() => {
-    return formatForm(form);
-  }, [form]);
-  const { validation, checkIsValid, disabled } = useFormValidation(checkedForm);
-
-  const classes = useStyles();
-
-  const handleInputChange = (key: 'roleName' | 'password', value: string) => {
-    setForm(v => ({ ...v, [key]: value }));
-  };
-
-  const createConfigs: ITextfieldConfig[] = [
-    {
-      label: userTrans('role'),
-      key: 'roleName',
-      onChange: (value: string) => handleInputChange('roleName', value),
-      variant: 'filled',
-      className: classes.input,
-      placeholder: userTrans('role'),
-      fullWidth: true,
-      validations: [
-        {
-          rule: 'require',
-          errorText: warningTrans('required', {
-            name: userTrans('role'),
-          }),
-        },
-      ],
-      defaultValue: form.roleName,
-    },
-  ];
-
-  const handleCreateRole = () => {
-    handleCreate(form);
-  };
-
-  return (
-    <DialogTemplate
-      title={userTrans('createRoleTitle')}
-      handleClose={handleClose}
-      confirmLabel={btnTrans('create')}
-      handleConfirm={handleCreateRole}
-      confirmDisabled={disabled}
-    >
-      <>
-        {createConfigs.map(v => (
-          <CustomInput
-            type="text"
-            textConfig={v}
-            checkValid={checkIsValid}
-            validInfo={validation}
-            key={v.label}
-          />
-        ))}
-      </>
-    </DialogTemplate>
-  );
-};
-
-export default CreateRole;

+ 271 - 0
client/src/pages/user/CreateRoleDialog.tsx

@@ -0,0 +1,271 @@
+import {
+  makeStyles,
+  Theme,
+  Typography,
+  Checkbox,
+  FormGroup,
+  FormControlLabel,
+} from '@material-ui/core';
+import { FC, useMemo, useState, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import DialogTemplate from '@/components/customDialog/DialogTemplate';
+import CustomInput from '@/components/customInput/CustomInput';
+import { ITextfieldConfig } from '@/components/customInput/Types';
+import { useFormValidation } from '@/hooks/Form';
+import { formatForm } from '@/utils/Form';
+import { UserHttp } from '@/http/User';
+import { CreateRoleProps, CreateRoleParams, Privilege } from './Types';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  input: {
+    margin: theme.spacing(1, 0, 0.5),
+  },
+  dialogWrapper: {
+    maxWidth: theme.spacing(88),
+  },
+  checkBox: {
+    width: theme.spacing(24),
+  },
+  formGrp: {
+    marginBottom: theme.spacing(2),
+  },
+  subTitle: {
+    marginBottom: theme.spacing(0.5),
+  },
+}));
+
+const CreateRoleDialog: FC<CreateRoleProps> = ({ onCreate, handleClose }) => {
+  const { t: commonTrans } = useTranslation();
+  const { t: userTrans } = useTranslation('user');
+  const { t: btnTrans } = useTranslation('btn');
+  const { t: warningTrans } = useTranslation('warning');
+  const [rbacOptions, setRbacOptions] = useState({
+    GlobalPrivileges: {},
+    CollectionPrivileges: {},
+    RbacObjects: {},
+    UserPrivileges: {},
+    Privileges: {},
+  });
+
+  const fetchRBAC = async () => {
+    const rbacOptions = await UserHttp.getRBAC();
+    const roles = await UserHttp.getRoles();
+
+    console.log(rbacOptions, roles);
+
+    setRbacOptions(rbacOptions);
+  };
+
+  useEffect(() => {
+    fetchRBAC();
+  }, []);
+
+  const [form, setForm] = useState<CreateRoleParams>({
+    roleName: '',
+    privileges: [],
+  });
+  const checkedForm = useMemo(() => {
+    return formatForm(form);
+  }, [form]);
+  const { validation, checkIsValid, disabled } = useFormValidation(checkedForm);
+
+  const classes = useStyles();
+
+  const handleInputChange = (key: 'roleName', value: string) => {
+    setForm(v => {
+      const newFrom = { ...v, [key]: value };
+
+      // update roleName
+      newFrom.privileges.forEach(p => (p.roleName = value));
+
+      return newFrom;
+    });
+  };
+
+  const createConfigs: ITextfieldConfig[] = [
+    {
+      label: userTrans('role'),
+      key: 'roleName',
+      onChange: (value: string) => handleInputChange('roleName', value),
+      variant: 'filled',
+      className: classes.input,
+      placeholder: userTrans('role'),
+      fullWidth: true,
+      validations: [
+        {
+          rule: 'require',
+          errorText: warningTrans('required', {
+            name: userTrans('role'),
+          }),
+        },
+      ],
+      defaultValue: form.roleName,
+    },
+  ];
+
+  const handleCreateRole = async () => {
+    await UserHttp.createRole(form);
+    await UserHttp.updateRolePrivileges(form);
+
+    onCreate(form);
+  };
+
+  // prepare data
+  const globalPriviledgeOptions = Object.values(rbacOptions.GlobalPrivileges);
+  const collectionPrivilegeOptions = Object.values(
+    rbacOptions.CollectionPrivileges
+  );
+  const userPrivilegeOptions = Object.values(rbacOptions.UserPrivileges);
+
+  return (
+    <DialogTemplate
+      title={userTrans('createRoleTitle')}
+      handleClose={handleClose}
+      confirmLabel={btnTrans('create')}
+      handleConfirm={handleCreateRole}
+      confirmDisabled={disabled}
+      dialogClass={classes.dialogWrapper}
+    >
+      <>
+        {createConfigs.map(v => (
+          <CustomInput
+            type="text"
+            textConfig={v}
+            checkValid={checkIsValid}
+            validInfo={validation}
+            key={v.label}
+          />
+        ))}
+        <Typography variant="h5" component="h5" className={classes.subTitle}>
+          {userTrans('privileges')}
+        </Typography>
+
+        <Typography variant="h6" component="h6" className={classes.subTitle}>
+          {userTrans('objectGlobal')}
+        </Typography>
+
+        <FormGroup row className={classes.formGrp}>
+          {globalPriviledgeOptions.map((r: any, index: number) => (
+            <FormControlLabel
+              control={
+                <Checkbox
+                  onChange={(
+                    e: React.ChangeEvent<HTMLInputElement>,
+                    checked: boolean
+                  ) => {
+                    let newPivilegs = [...form.privileges];
+
+                    if (!checked) {
+                      newPivilegs = newPivilegs.filter(
+                        (n: Privilege) => n.privilegeName !== r
+                      );
+                    } else {
+                      newPivilegs.push({
+                        privilegeName: r,
+                        object: 'Global',
+                        objectName: '*',
+                        roleName: form.roleName,
+                      });
+                    }
+                    console.log(newPivilegs);
+
+                    setForm(v => ({ ...v, privileges: [...newPivilegs] }));
+                  }}
+                />
+              }
+              key={r}
+              label={r}
+              value={r}
+              className={classes.checkBox}
+            />
+          ))}
+        </FormGroup>
+
+        <Typography variant="h6" component="h6" className={classes.subTitle}>
+          {userTrans('objectCollection')}
+        </Typography>
+
+        <FormGroup row className={classes.formGrp}>
+          {collectionPrivilegeOptions.map((r: any, index: number) => (
+            <FormControlLabel
+              control={
+                <Checkbox
+                  onChange={(
+                    e: React.ChangeEvent<HTMLInputElement>,
+                    checked: boolean
+                  ) => {
+                    let newPivilegs = [...form.privileges];
+
+                    if (!checked) {
+                      newPivilegs = newPivilegs.filter(
+                        (n: Privilege) => n.privilegeName !== r
+                      );
+                    } else {
+                      newPivilegs.push({
+                        privilegeName: r,
+                        object: 'Collection',
+                        objectName: '*',
+                        roleName: form.roleName,
+                      });
+                    }
+                    console.log(newPivilegs);
+
+                    setForm(v => ({ ...v, privileges: [...newPivilegs] }));
+                  }}
+                />
+              }
+              key={r}
+              label={r}
+              value={r}
+              className={classes.checkBox}
+            />
+          ))}
+        </FormGroup>
+
+        <Typography variant="h6" component="h6" className={classes.subTitle}>
+          {userTrans('objectUser')}
+        </Typography>
+
+        <FormGroup row className={classes.formGrp}>
+          {userPrivilegeOptions.map((r: any, index: number) => (
+            <FormControlLabel
+              control={
+                <Checkbox
+                  onChange={(
+                    e: React.ChangeEvent<HTMLInputElement>,
+                    checked: boolean
+                  ) => {
+                    let newPivilegs = [...form.privileges];
+
+                    if (!checked) {
+                      newPivilegs = newPivilegs.filter(
+                        (n: Privilege) => n.privilegeName !== r
+                      );
+                    } else {
+                      newPivilegs.push({
+                        privilegeName: r,
+                        object: 'User',
+                        objectName: '*',
+                        roleName: form.roleName,
+                      });
+                    }
+
+                    console.log(newPivilegs);
+
+                    setForm(v => ({ ...v, privileges: [...newPivilegs] }));
+                  }}
+                />
+              }
+              key={r}
+              label={r}
+              value={r}
+              className={classes.checkBox}
+            />
+          ))}
+        </FormGroup>
+      </>
+    </DialogTemplate>
+  );
+};
+
+export default CreateRoleDialog;

+ 1 - 1
client/src/pages/user/CreateUser.tsx

@@ -18,7 +18,7 @@ import { Option as RoleOption } from '@/components/customSelector/Types';
 
 
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
   input: {
   input: {
-    margin: theme.spacing(2, 0, 0.5),
+    margin: theme.spacing(1, 0, 0.5),
   },
   },
   dialogWrapper: {
   dialogWrapper: {
     maxWidth: theme.spacing(70),
     maxWidth: theme.spacing(70),

+ 5 - 9
client/src/pages/user/Roles.tsx

@@ -1,15 +1,15 @@
-import React, { useContext, useEffect, useState } from 'react';
+import { useContext, useEffect, useState } from 'react';
 import { makeStyles, Theme } from '@material-ui/core';
 import { makeStyles, Theme } from '@material-ui/core';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import { UserHttp } from '@/http/User';
 import { UserHttp } from '@/http/User';
 import AttuGrid from '@/components/grid/Grid';
 import AttuGrid from '@/components/grid/Grid';
 import { ColDefinitionsType, ToolBarConfig } from '@/components/grid/Types';
 import { ColDefinitionsType, ToolBarConfig } from '@/components/grid/Types';
-import { CreateRoleParams, DeleteRoleParams, RoleData } from './Types';
+import { DeleteRoleParams, RoleData } from './Types';
 import DeleteTemplate from '@/components/customDialog/DeleteDialogTemplate';
 import DeleteTemplate from '@/components/customDialog/DeleteDialogTemplate';
 import { rootContext } from '@/context/Root';
 import { rootContext } from '@/context/Root';
 import { useNavigationHook } from '@/hooks/Navigation';
 import { useNavigationHook } from '@/hooks/Navigation';
 import { ALL_ROUTER_TYPES } from '@/router/Types';
 import { ALL_ROUTER_TYPES } from '@/router/Types';
-import CreateRole from './CreateRole';
+import CreateRole from './CreateRoleDialog';
 
 
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
   wrapper: {
   wrapper: {
@@ -36,8 +36,7 @@ const Roles = () => {
     setRoles(roles.results.map((v: any) => ({ name: v.role.name })));
     setRoles(roles.results.map((v: any) => ({ name: v.role.name })));
   };
   };
 
 
-  const handleCreate = async (data: CreateRoleParams) => {
-    await UserHttp.createRole(data);
+  const onCreate = async () => {
     fetchRoles();
     fetchRoles();
     openSnackBar(successTrans('create', { name: userTrans('role') }));
     openSnackBar(successTrans('create', { name: userTrans('role') }));
     handleCloseDialog();
     handleCloseDialog();
@@ -65,10 +64,7 @@ const Roles = () => {
           type: 'custom',
           type: 'custom',
           params: {
           params: {
             component: (
             component: (
-              <CreateRole
-                handleCreate={handleCreate}
-                handleClose={handleCloseDialog}
-              />
+              <CreateRole onCreate={onCreate} handleClose={handleCloseDialog} />
             ),
             ),
           },
           },
         });
         });

+ 9 - 1
client/src/pages/user/Types.ts

@@ -46,12 +46,20 @@ export interface DeleteUserParams {
   username: string;
   username: string;
 }
 }
 
 
+export interface Privilege {
+  roleName: string;
+  object: string;
+  objectName: string;
+  privilegeName: string;
+}
+
 export interface CreateRoleParams {
 export interface CreateRoleParams {
   roleName: string;
   roleName: string;
+  privileges: Privilege[];
 }
 }
 
 
 export interface CreateRoleProps {
 export interface CreateRoleProps {
-  handleCreate: (data: CreateRoleParams) => void;
+  onCreate: (data: CreateRoleParams) => void;
   handleClose: () => void;
   handleClose: () => void;
 }
 }
 
 

+ 1 - 1
client/src/pages/user/Update.tsx

@@ -10,7 +10,7 @@ import { UpdateUserParams, UpdateUserProps } from './Types';
 
 
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
   input: {
   input: {
-    margin: theme.spacing(3, 0, 0.5),
+    margin: theme.spacing(1, 0, 0.5),
   },
   },
 }));
 }));
 
 

+ 1 - 1
client/src/pages/user/UpdateUserRole.tsx

@@ -13,7 +13,7 @@ import { UserHttp } from '@/http/User';
 
 
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
   input: {
   input: {
-    margin: theme.spacing(2, 0, 0.5),
+    margin: theme.spacing(1, 0, 0.5),
   },
   },
   dialogWrapper: {
   dialogWrapper: {
     maxWidth: theme.spacing(70),
     maxWidth: theme.spacing(70),

+ 1 - 1
server/package.json

@@ -12,7 +12,7 @@
     "url": "https://github.com/zilliztech/attu"
     "url": "https://github.com/zilliztech/attu"
   },
   },
   "dependencies": {
   "dependencies": {
-    "@zilliz/milvus2-sdk-node": "2.2.22-beta.1",
+    "@zilliz/milvus2-sdk-node": "2.2.22-beta.2",
     "axios": "^1.4.0",
     "axios": "^1.4.0",
     "chalk": "^4.1.2",
     "chalk": "^4.1.2",
     "class-sanitizer": "^1.0.1",
     "class-sanitizer": "^1.0.1",

+ 83 - 16
server/src/users/users.controller.ts

@@ -20,44 +20,45 @@ export class UserController {
   }
   }
 
 
   generateRoutes() {
   generateRoutes() {
+    // user
     this.router.get('/', this.getUsers.bind(this));
     this.router.get('/', this.getUsers.bind(this));
-
     this.router.post(
     this.router.post(
       '/',
       '/',
       dtoValidationMiddleware(CreateUserDto),
       dtoValidationMiddleware(CreateUserDto),
       this.createUsers.bind(this)
       this.createUsers.bind(this)
     );
     );
-
     this.router.put(
     this.router.put(
       '/',
       '/',
       dtoValidationMiddleware(UpdateUserDto),
       dtoValidationMiddleware(UpdateUserDto),
       this.updateUsers.bind(this)
       this.updateUsers.bind(this)
     );
     );
-
     this.router.delete('/:username', this.deleteUser.bind(this));
     this.router.delete('/:username', this.deleteUser.bind(this));
-
-    this.router.get('/roles', this.getRoles.bind(this));
-
-    this.router.post(
-      '/roles',
-      dtoValidationMiddleware(CreateRoleDto),
-      this.createRole.bind(this)
-    );
-
-    this.router.delete('/roles/:roleName', this.deleteRole.bind(this));
-
     this.router.put(
     this.router.put(
       '/:username/role/update',
       '/:username/role/update',
       dtoValidationMiddleware(AssignUserRoleDto),
       dtoValidationMiddleware(AssignUserRoleDto),
       this.updateUserRole.bind(this)
       this.updateUserRole.bind(this)
     );
     );
-
     this.router.put(
     this.router.put(
       '/:username/role/unassign',
       '/:username/role/unassign',
       dtoValidationMiddleware(UnassignUserRoleDto),
       dtoValidationMiddleware(UnassignUserRoleDto),
       this.unassignUserRole.bind(this)
       this.unassignUserRole.bind(this)
     );
     );
 
 
+    // role
+    this.router.get('/rbac', this.rbac.bind(this));
+    this.router.get('/roles', this.getRoles.bind(this));
+    this.router.post(
+      '/roles',
+      dtoValidationMiddleware(CreateRoleDto),
+      this.createRole.bind(this)
+    );
+    this.router.get('/roles/:roleName', this.listGrant.bind(this));
+    this.router.delete('/roles/:roleName', this.deleteRole.bind(this));
+    this.router.put(
+      '/roles/:roleName/updatePrivileges',
+      this.updateRolePrivileges.bind(this)
+    );
+
     return this.router;
     return this.router;
   }
   }
 
 
@@ -107,7 +108,15 @@ export class UserController {
 
 
   async getRoles(req: Request, res: Response, next: NextFunction) {
   async getRoles(req: Request, res: Response, next: NextFunction) {
     try {
     try {
-      const result = await this.userService.getRoles();
+      const result = (await this.userService.getRoles()) as any;
+
+      for (let i = 0; i < result.results.length; i++) {
+        const { entities } = await this.userService.listGrants({
+          roleName: result.results[i].role.name,
+        });
+        result.results[i].entities = entities;
+      }
+
       res.send(result);
       res.send(result);
     } catch (error) {
     } catch (error) {
       next(error);
       next(error);
@@ -187,4 +196,62 @@ export class UserController {
       next(error);
       next(error);
     }
     }
   }
   }
+
+  async rbac(req: Request, res: Response, next: NextFunction) {
+    try {
+      const result = await this.userService.getRBAC();
+      res.send(result);
+    } catch (error) {
+      next(error);
+    }
+  }
+
+  async listGrant(req: Request, res: Response, next: NextFunction) {
+    const { roleName } = req.params;
+    try {
+      const result = await this.userService.listGrants({
+        roleName: roleName,
+      });
+      res.send(result);
+    } catch (error) {
+      next(error);
+    }
+  }
+
+  async updateRolePrivileges(req: Request, res: Response, next: NextFunction) {
+    const { privileges } = req.body;
+    const { roleName } = req.params;
+
+    const results = [];
+
+    try {
+      // get existing privileges
+      const existingPrivileges = await this.userService.listGrants({
+        roleName,
+      });
+
+      console.log('existing privileges', existingPrivileges);
+
+      // const existingPrivileges = privileges.results[0].roles;
+      // // remove user existing roles
+      // for (let i = 0; i < existingRoles.length; i++) {
+      //   if (existingRoles[i].name.length > 0) {
+      //     await this.userService.unassignUserRole({
+      //       username,
+      //       roleName: existingRoles[i].name,
+      //     });
+      //   }
+      // }
+
+      // assign new user roles
+      for (let i = 0; i < privileges.length; i++) {
+        const result = await this.userService.grantRolePrivilege(privileges[i]);
+        results.push(result);
+      }
+
+      res.send(results);
+    } catch (error) {
+      next(error);
+    }
+  }
 }
 }

+ 29 - 0
server/src/users/users.service.ts

@@ -10,6 +10,13 @@ import {
   HasRoleReq,
   HasRoleReq,
   listRoleReq,
   listRoleReq,
   SelectUserReq,
   SelectUserReq,
+  Privileges,
+  GlobalPrivileges,
+  CollectionPrivileges,
+  UserPrivileges,
+  RbacObjects,
+  ListGrantsReq,
+  OperateRolePrivilegeReq,
 } from '@zilliz/milvus2-sdk-node';
 } from '@zilliz/milvus2-sdk-node';
 import { throwErrorFromSDK } from '../utils/Error';
 import { throwErrorFromSDK } from '../utils/Error';
 
 
@@ -87,4 +94,26 @@ export class UserService {
     throwErrorFromSDK(res.status);
     throwErrorFromSDK(res.status);
     return res;
     return res;
   }
   }
+
+  async getRBAC() {
+    return {
+      Privileges,
+      GlobalPrivileges,
+      CollectionPrivileges,
+      UserPrivileges,
+      RbacObjects,
+    };
+  }
+
+  async listGrants(data: ListGrantsReq) {
+    const res = await this.milvusService.client.listGrants(data);
+    throwErrorFromSDK(res.status);
+    return res;
+  }
+
+  async grantRolePrivilege(data: OperateRolePrivilegeReq) {
+    const res = await this.milvusService.client.grantRolePrivilege(data);
+    throwErrorFromSDK(res);
+    return res;
+  }
 }
 }

+ 4 - 4
server/yarn.lock

@@ -1180,10 +1180,10 @@
   resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.9.tgz#b6ef7457e826be8049667ae673eda7876eb049be"
   resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.9.tgz#b6ef7457e826be8049667ae673eda7876eb049be"
   integrity sha512-4VSbbcMoxc4KLjb1gs96SRmi7w4h1SF+fCoiK0XaQX62buCc1G5d0DC5bJ9xJBNPDSVCmIrcl8BiYxzjrqaaJA==
   integrity sha512-4VSbbcMoxc4KLjb1gs96SRmi7w4h1SF+fCoiK0XaQX62buCc1G5d0DC5bJ9xJBNPDSVCmIrcl8BiYxzjrqaaJA==
 
 
-"@zilliz/milvus2-sdk-node@2.2.21-beta.1":
-  version "2.2.22-beta.1"
-  resolved "https://registry.yarnpkg.com/@zilliz/milvus2-sdk-node/-/milvus2-sdk-node-2.2.22-beta.1.tgz#c58456c6c82bd7a5b34b3b3a6360d818233ad618"
-  integrity sha512-PrG1AWbv8HvHFbfKrXCbSsFFVLoWBfRfNcqJjSH1Lh5Av6RtWOuSz4wfO+7Ko78+vJ1RZtIe4zfFUezp83feEA==
+"@zilliz/milvus2-sdk-node@2.2.22-beta.2":
+  version "2.2.22-beta.2"
+  resolved "https://registry.yarnpkg.com/@zilliz/milvus2-sdk-node/-/milvus2-sdk-node-2.2.22-beta.2.tgz#e1fb9ab267252054b4f0b29c0f41d598faa24662"
+  integrity sha512-knz8YQKbT6LHblTbFILnC/XLZGIgzOpdPUFQuz27G10GDEIv6z6chxsNpHUwfGrQCW4eVz6DKtnSs1tnibfA5Q==
   dependencies:
   dependencies:
     "@grpc/grpc-js" "1.8.17"
     "@grpc/grpc-js" "1.8.17"
     "@grpc/proto-loader" "0.7.7"
     "@grpc/proto-loader" "0.7.7"