Repository: dragonflyoss/Dragonfly2
Pull request #570 (2021-08-23 09:51:38)

Add a function to check task existence in cdn
When cdn check task existence in gc, it will call taskMgr.Get which will update the accessTimeMap in manager. I add a Exist method to replace the Get in gc.

codecov-commenter: # [Codecov](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/570?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) Report
> Merging [#570](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/570?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (0a4bc2f) into [main](https://codecov.io/gh/dragonflyoss/Dragonfly2/commit/c34a6e715745d7684c229a2c667721c48277680a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (c34a6e7) will **decrease** coverage by `0.01%`.
> The diff coverage is `0.00%`.

[![Impacted file tree graph](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/570/graphs/tree.svg?width=650&height=150&src=pr&token=PUICJYSAT7&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/570?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)

```diff
@@            Coverage Diff             @@
##             main     #570      +/-   ##
==========================================
- Coverage   39.28%   39.26%   -0.02%     
==========================================
  Files          86       86              
  Lines        6641     6644       +3     
==========================================
  Hits         2609     2609              
- Misses       3661     3665       +4     
+ Partials      371      370       -1     
```

| Flag | Coverage Δ | |
|---|---|---|
| e2etests | `?` | |
| unittests | `39.26% <0.00%> (-0.02%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/570?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) | Coverage Δ | |
|---|---|---|
| [cdnsystem/supervisor/task/manager.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/570/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2Ruc3lzdGVtL3N1cGVydmlzb3IvdGFzay9tYW5hZ2VyLmdv) | `10.09% <0.00%> (-0.29%)` | :arrow_down: |
| [client/daemon/peer/peertask\_stream.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/570/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX3N0cmVhbS5nbw==) | `55.05% <0.00%> (-2.44%)` | :arrow_down: |
| [client/daemon/peer/peertask\_file.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/570/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX2ZpbGUuZ28=) | `54.44% <0.00%> (+0.38%)` | :arrow_up: |
| [client/daemon/peer/peertask\_base.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/570/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX2Jhc2UuZ28=) | `35.31% <0.00%> (+1.27%)` | :arrow_up: |


Patch:
diff --git a/cdnsystem/supervisor/cdn/storage/disk/disk.go b/cdnsystem/supervisor/cdn/storage/disk/disk.go
index c35377c4..f058c5c8 100644
--- a/cdnsystem/supervisor/cdn/storage/disk/disk.go
+++ b/cdnsystem/supervisor/cdn/storage/disk/disk.go
@@ -131,10 +131,7 @@ func (s *diskStorageMgr) GC() error {
 	for _, taskID := range gcTaskIDs {
 		synclock.Lock(taskID, false)
 		// try to ensure the taskID is not using again
-		if _, err := s.taskMgr.Get(taskID); err == nil || !cdnerrors.IsDataNotFound(err) {
-			if err != nil {
-				logger.GcLogger.With("type", "disk").Errorf("failed to get taskID(%s): %v", taskID, err)
-			}
+		if s.taskMgr.Exist(taskID) {
 			synclock.UnLock(taskID, false)
 			continue
 		}
diff --git a/cdnsystem/supervisor/mock/mock_task_mgr.go b/cdnsystem/supervisor/mock/mock_task_mgr.go
index caf229d7..ab451779 100644
--- a/cdnsystem/supervisor/mock/mock_task_mgr.go
+++ b/cdnsystem/supervisor/mock/mock_task_mgr.go
@@ -50,6 +50,20 @@ func (mr *MockSeedTaskMgrMockRecorder) Delete(arg0 interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockSeedTaskMgr)(nil).Delete), arg0)
 }
 
+// Exist mocks base method.
+func (m *MockSeedTaskMgr) Exist(arg0 string) bool {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Exist", arg0)
+	ret0, _ := ret[0].(bool)
+	return ret0
+}
+
+// Exist indicates an expected call of Exist.
+func (mr *MockSeedTaskMgrMockRecorder) Exist(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exist", reflect.TypeOf((*MockSeedTaskMgr)(nil).Exist), arg0)
+}
+
 // Get mocks base method.
 func (m *MockSeedTaskMgr) Get(arg0 string) (*types.SeedTask, error) {
 	m.ctrl.T.Helper()
diff --git a/cdnsystem/supervisor/task/manager.go b/cdnsystem/supervisor/task/manager.go
index 60c17f77..538feb95 100644
--- a/cdnsystem/supervisor/task/manager.go
+++ b/cdnsystem/supervisor/task/manager.go
@@ -165,10 +165,6 @@ func (tm *Manager) getTask(taskID string) (*types.SeedTask, error) {
 		}
 		return nil, err
 	}
-	// update accessTime for taskID
-	if err := tm.accessTimeMap.Add(taskID, time.Now()); err != nil {
-		logger.WithTaskID(taskID).Warnf("failed to update accessTime: %v", err)
-	}
 	// type assertion
 	if info, ok := v.(*types.SeedTask); ok {
 		return info, nil
@@ -177,7 +173,17 @@ func (tm *Manager) getTask(taskID string) (*types.SeedTask, error) {
 }
 
 func (tm Manager) Get(taskID string) (*types.SeedTask, error) {
-	return tm.getTask(taskID)
+	task, err := tm.getTask(taskID)
+	// update accessTime for taskID
+	if err := tm.accessTimeMap.Add(taskID, time.Now()); err != nil {
+		logger.WithTaskID(taskID).Warnf("failed to update accessTime: %v", err)
+	}
+	return task, err
+}
+
+func (tm Manager) Exist(taskID string) bool {
+	_, err := tm.taskStore.Get(taskID)
+	return err == nil || !cdnerrors.IsDataNotFound(err)
 }
 
 func (tm Manager) GetAccessTime() (*syncmap.SyncMap, error) {
diff --git a/cdnsystem/supervisor/task_mgr.go b/cdnsystem/supervisor/task_mgr.go
index 35811717..1b383ec3 100644
--- a/cdnsystem/supervisor/task_mgr.go
+++ b/cdnsystem/supervisor/task_mgr.go
@@ -35,6 +35,9 @@ type SeedTaskMgr interface {
 	// Get get task Info with specified taskId.
 	Get(string) (*types.SeedTask, error)
 
+	// Exist check task existence with specified taskId.
+	Exist(string) bool
+
 	// GetAccessTime get all tasks accessTime.
 	GetAccessTime() (*syncmap.SyncMap, error)
 
diff --git a/manager/handlers/permission.go b/manager/handlers/permission.go
index 08df9a2e..d65a9ee8 100644
--- a/manager/handlers/permission.go
+++ b/manager/handlers/permission.go
@@ -23,92 +23,101 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
-// @Summary Get PermissionGroups
-// @Description Get PermissionGroups
+// @Summary Get Permissions
+// @Description Get Permissions
 // @Tags permission
 // @Produce json
-// @Success 200 {object} types.PermissionGroups
-// @Failure 400
-// @Failure 500
-// @Router /permission/groups [get]
+// @Success 200 {object} RoutesInfo
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /permissions [get]
 
-func (h *Handlers) GetPermissionGroups(g *gin.Engine) func(ctx *gin.Context) {
+func (h *Handlers) GetPermissions(g *gin.Engine) func(ctx *gin.Context) {
 	return func(ctx *gin.Context) {
 
-		permissionGroups := h.Service.GetPermissionGroups(g)
+		permissionGroups := h.Service.GetPermissions(g)
 
 		ctx.JSON(http.StatusOK, permissionGroups)
 	}
 }
 
-// @Summary Get User Roles
-// @Description Get User Roles
-// @Tags permission
+// @Summary Create Role
+// @Description Create Role by json config
+// @Tags role
+// @Accept json
 // @Produce json
-// @Success 200 {object} []map[string]string
-// @Failure 400
-// @Failure 500
-// @Router /permission/roles/{subject} [get]
+// @Success 200
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /roles [post]
 
-func (h *Handlers) GetRolesForUser(ctx *gin.Context) {
-	var params types.UserRolesParams
-	if err := ctx.ShouldBindUri(&params); err != nil {
+func (h *Handlers) CreateRole(ctx *gin.Context) {
+	var json types.CreateRolePermissionRequest
+	if err := ctx.ShouldBindJSON(&json); err != nil {
 		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
 		return
 	}
-	roles, err := h.Service.GetRolesForUser(params.Subject)
+	err := h.Service.CreateRole(json)
 	if err != nil {
 		ctx.Error(err)
 		return
 	}
-	ctx.JSON(http.StatusOK, gin.H{"roles": roles})
-
+	ctx.Status(http.StatusOK)
 }
 
-// @Summary Judge User Role
-// @Description Judge User Role
-// @Tags permission
+// @Summary Update Role
+// @Description Remove Role Permission by json config
+// @Tags role
+// @Accept json
 // @Produce json
-// @Success 200 {object}
-// @Failure 400
-// @Failure 500
-// @Router /permission/{subject}/{object}/{action} [get]
+// @Success 200
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /roles/:role_name/permission [delete]
 
-func (h *Handlers) HasRoleForUser(ctx *gin.Context) {
-	var params types.UserHasRoleParams
+func (h *Handlers) RemoveRolePermission(ctx *gin.Context) {
+
+	var params types.RoleParams
 	if err := ctx.ShouldBindUri(&params); err != nil {
 		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
 		return
 	}
-	if params.Subject == "admin" {
-		ctx.JSON(http.StatusOK, gin.H{"has": true})
+	var json types.ObjectPermission
+	if err := ctx.ShouldBindJSON(&json); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
 		return
 	}
-	has, err := h.Service.HasRoleForUser(params.Subject, params.Object, params.Action)
+	err := h.Service.RemoveRolePermission(params.RoleName, json)
 	if err != nil {
 		ctx.Error(err)
 		return
 	}
-	ctx.JSON(http.StatusOK, gin.H{"has": has})
+	ctx.Status(http.StatusOK)
 }
 
-// @Summary Create Permission
-// @Description Create Permission by json config
-// @Tags permission
+// @Summary Update Role
+// @Description Add Role Permission by json config
+// @Tags role
 // @Accept json
 // @Produce json
 // @Success 200
-// @Failure 400
-// @Failure 500
-// @Router /permission [post]
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /roles/:role_name/permission [post]
 
-func (h *Handlers) CreatePermission(ctx *gin.Context) {
-	var json types.PolicyRequest
+func (h *Handlers) AddRolePermission(ctx *gin.Context) {
+
+	var params types.RoleParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+	var json types.ObjectPermission
 	if err := ctx.ShouldBindJSON(&json); err != nil {
 		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
 		return
 	}
-	err := h.Service.CreatePermission(json)
+	err := h.Service.AddRolePermission(params.RoleName, json)
 	if err != nil {
 		ctx.Error(err)
 		return
@@ -116,23 +125,57 @@ func (h *Handlers) CreatePermission(ctx *gin.Context) {
 	ctx.Status(http.StatusOK)
 }
 
+// @Summary Get Roles
+// @Description Get Roles by name
+// @Tags role
+// @Accept text
+// @Produce json
+// @Success 200
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /roles [get]
+
+func (h *Handlers) GetRoles(ctx *gin.Context) {
+	roles := h.Service.GetRoles()
+	ctx.JSON(http.StatusOK, roles)
+}
+
+// @Summary Get Role
+// @Description Get Role
+// @Tags permission
+// @Accept text
+// @Produce json
+// @Success 200
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /roles/:role_name [get]
+
+func (h *Handlers) GetRole(ctx *gin.Context) {
+	var params types.RoleParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+	ctx.JSON(http.StatusOK, h.Service.GetRole(params.RoleName))
+}
+
 // @Summary Destroy Permission
 // @Description Destroy Permission by json config
 // @Tags permission
 // @Accept json
 // @Produce json
 // @Success 200
-// @Failure 400
-// @Failure 500
-// @Router /permission [delete]
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /role/:role_name [delete]
 
-func (h *Handlers) DestroyPermission(ctx *gin.Context) {
-	var json types.PolicyRequest
-	if err := ctx.ShouldBindJSON(&json); err != nil {
+func (h *Handlers) DestroyRole(ctx *gin.Context) {
+	var params types.RoleParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
 		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
 		return
 	}
-	err := h.Service.DestroyPermission(json)
+	err := h.Service.DestroyRole(params.RoleName)
 	if err != nil {
 		ctx.Error(err)
 		return
diff --git a/manager/handlers/user.go b/manager/handlers/user.go
index 2fd638a7..a36076af 100644
--- a/manager/handlers/user.go
+++ b/manager/handlers/user.go
@@ -48,3 +48,75 @@ func (h *Handlers) SignUp(ctx *gin.Context) {
 
 	ctx.JSON(http.StatusOK, user)
 }
+
+// @Summary Delete Role For User
+// @Description Delete Role For User by uri config
+// @Tags users
+// @Accept text
+// @Produce json
+// @Success 200
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /users/:id/roles/:role_name [delete]
+
+func (h *Handlers) DeleteRoleForUser(ctx *gin.Context) {
+	var params types.RoleRequest
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+	err := h.Service.DeleteRoleForUser(params.ID, params.RoleName)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+	ctx.Status(http.StatusOK)
+}
+
+// @Summary Add Role For User
+// @Description Add Role For User by uri config
+// @Tags users
+// @Accept text
+// @Produce json
+// @Success 200
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /users/:id/roles/:role_name [post]
+
+func (h *Handlers) AddRoleToUser(ctx *gin.Context) {
+	var params types.RoleRequest
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+	err := h.Service.AddRoleForUser(params.ID, params.RoleName)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+	ctx.Status(http.StatusOK)
+}
+
+// @Summary Get User Roles
+// @Description Get User Roles
+// @Tags User
+// @Produce json
+// @Success 200 {object} RoutesInfo
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /users/:id/roles [get]
+
+func (h *Handlers) GetRolesForUser(ctx *gin.Context) {
+	var params types.UserParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+	roles, err := h.Service.GetRolesForUser(params.ID, ctx.GetString("userName"))
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+	ctx.JSON(http.StatusOK, roles)
+
+}
diff --git a/manager/manager.go b/manager/manager.go
index 3fd8fb25..8144e7c8 100644
--- a/manager/manager.go
+++ b/manager/manager.go
@@ -83,6 +83,12 @@ func New(cfg *config.Config) (*Server, error) {
 	if err != nil {
 		return nil, err
 	}
+
+	// Initialize roles and check roles
+	err = rbac.InitRole(enforcer, router)
+	if err != nil {
+		return nil, err
+	}
 	restServer := &http.Server{
 		Addr:    cfg.Server.REST.Addr,
 		Handler: router,
diff --git a/manager/middlewares/jwt.go b/manager/middlewares/jwt.go
index dd2a8cad..3a756e77 100644
--- a/manager/middlewares/jwt.go
+++ b/manager/middlewares/jwt.go
@@ -29,6 +29,7 @@ import (
 
 type user struct {
 	userName string
+	ID       uint
 }
 
 func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
@@ -42,7 +43,7 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
 
 		IdentityHandler: func(c *gin.Context) interface{} {
 			claims := jwt.ExtractClaims(c)
-			userNmae, ok := claims[identityKey]
+			userName, ok := claims[identityKey]
 			if !ok {
 				c.JSON(http.StatusUnauthorized, gin.H{
 					"message": "Unavailable token: require username info",
@@ -50,10 +51,20 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
 				c.Abort()
 				return nil
 			}
+			userID, ok := claims["ID"]
+			if !ok {
+				c.JSON(http.StatusUnauthorized, gin.H{
+					"message": "Unavailable token: require id info",
+				})
+				c.Abort()
+				return nil
+			}
 			u := &user{
-				userName: userNmae.(string),
+				userName: userName.(string),
+				ID:       uint(userID.(float64)),
 			}
 			c.Set("userName", u.userName)
+			c.Set("userID", u.ID)
 			return u
 		},
 
@@ -75,6 +86,7 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
 			if u, ok := data.(*model.User); ok {
 				return jwt.MapClaims{
 					identityKey: u.Name,
+					"ID":        u.ID,
 				}
 			}
 			return jwt.MapClaims{}
diff --git a/manager/middlewares/rbac.go b/manager/middlewares/rbac.go
index 4870ba6f..5c47bbc0 100644
--- a/manager/middlewares/rbac.go
+++ b/manager/middlewares/rbac.go
@@ -19,6 +19,7 @@ package middlewares
 import (
 	"net/http"
 
+	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/manager/permission/rbac"
 	"github.com/casbin/casbin/v2"
 	"github.com/gin-gonic/gin"
@@ -29,7 +30,7 @@ func RBAC(e *casbin.Enforcer) gin.HandlerFunc {
 		userName := c.GetString("userName")
 		// request path
 		p := c.Request.URL.Path
-		permissionGroupName, err := rbac.GetAPIGroupName(p)
+		permissionName, err := rbac.GetAPIGroupName(p)
 		if err != nil {
 			c.Next()
 			return
@@ -50,10 +51,18 @@ func RBAC(e *casbin.Enforcer) gin.HandlerFunc {
 			c.Next()
 			return
 		}
-		res, err := e.Enforce(userName, permissionGroupName, action)
-		if err != nil || !res {
+		res, err := e.Enforce(userName, permissionName, action)
+		if err != nil {
+			logger.Errorf("RBAC validate error: %s", err)
 			c.JSON(http.StatusUnauthorized, gin.H{
-				"message": "permission validate error",
+				"message": "permission validate error, please see log!",
+			})
+			c.Abort()
+			return
+		}
+		if !res {
+			c.JSON(http.StatusUnauthorized, gin.H{
+				"message": "permission deny",
 			})
 			c.Abort()
 			return
diff --git a/manager/permission/rbac/rbac.go b/manager/permission/rbac/rbac.go
index c7d1628d..a7b47a28 100644
--- a/manager/permission/rbac/rbac.go
+++ b/manager/permission/rbac/rbac.go
@@ -18,6 +18,7 @@ package rbac
 
 import (
 	"errors"
+	"fmt"
 	"net/http"
 	"regexp"
 	"strings"
@@ -47,7 +48,7 @@ g = _, _
 e = some(where (p.eft == allow))
 
 [matchers]
-m = g(r.sub, p.sub) && r.obj == p.obj && (r.act == p.act || p.act == "*") || r.sub == "admin"
+m = g(r.sub, p.sub) && r.obj == p.obj && (r.act == p.act || p.act == "*")
 `
 
 func NewEnforcer(gdb *gorm.DB) (*casbin.Enforcer, error) {
@@ -69,12 +70,22 @@ func NewEnforcer(gdb *gorm.DB) (*casbin.Enforcer, error) {
 func InitRole(e *casbin.Enforcer, g *gin.Engine) error {
 	systemRoles := SystemRoles(g)
 
+	var err error
 	for _, role := range systemRoles {
 		roleInfo := strings.Split(role, ":")
-		_, err := e.AddPolicy(role, roleInfo[0], roleInfo[1])
+		_, err = e.AddPolicy(role, roleInfo[0], roleInfo[1])
 		if err != nil {
 			return err
 		}
+
+		// init admin permissions
+		_, err = e.AddPolicy("admin", roleInfo[0], "*")
+		if err != nil {
+			return err
+		}
+	}
+	if _, err := e.AddRoleForUser("admin", "admin"); err != nil {
+		return err
 	}
 	logger.Info("init and check role success")
 	return nil
@@ -95,14 +106,7 @@ func RoleName(object, action string) string {
 	if object == "admin" {
 		return "admin"
 	}
-	roleName := ""
-	switch action {
-	case "read":
-		roleName = object + ":" + "read"
-	case "write":
-		roleName = object + ":" + "*"
-	}
-	return roleName
+	return fmt.Sprintf("%s:%s", object, action)
 }
 
 func GetAPIGroupNames(g *gin.Engine) []string {
diff --git a/manager/router/router.go b/manager/router/router.go
index 53c2ea7d..39f62d52 100644
--- a/manager/router/router.go
+++ b/manager/router/router.go
@@ -19,7 +19,6 @@ package router
 import (
 	"d7y.io/dragonfly/v2/manager/handlers"
 	"d7y.io/dragonfly/v2/manager/middlewares"
-	rbacbase "d7y.io/dragonfly/v2/manager/permission/rbac"
 	"d7y.io/dragonfly/v2/manager/service"
 	"github.com/casbin/casbin/v2"
 	"github.com/gin-contrib/static"
@@ -61,7 +60,9 @@ func Init(verbose bool, publicPath string, service service.REST, enforcer *casbi
 	ai.POST("/signin", jwt.LoginHandler)
 	ai.POST("/signout", jwt.LogoutHandler)
 	ai.POST("/refresh_token", jwt.RefreshHandler)
-	ai.POST("/signup", jwt.MiddlewareFunc(), rbac, h.SignUp)
+	ai.POST("/:id/roles/:role_name", h.AddRoleToUser)
+	ai.DELETE("/:id/roles/:role_name", h.DeleteRoleForUser)
+	ai.POST("/signup", h.SignUp)
 
 	// Scheduler Cluster
 	sc := apiv1.Group("/scheduler-clusters")
@@ -98,13 +99,18 @@ func Init(verbose bool, publicPath string, service service.REST, enforcer *casbi
 	c.GET(":id", h.GetCDN)
 	c.GET("", h.GetCDNs)
 
+	// roles
+	re := apiv1.Group("/roles", jwt.MiddlewareFunc(), rbac)
+	re.POST("", h.CreateRole)
+	re.GET("", h.GetRoles)
+	re.DELETE("/:role_name", h.DestroyRole)
+	re.GET("/:role_name", h.GetRole)
+	re.POST("/:role_name/permission", h.AddRolePermission)
+	re.DELETE("/:role_name/permission", h.RemoveRolePermission)
+
 	// Permission
-	pn := apiv1.Group("/permission", jwt.MiddlewareFunc(), rbac)
-	pn.POST("", h.CreatePermission)
-	pn.DELETE("", h.DestroyPermission)
-	pn.GET("/groups", h.GetPermissionGroups(r))
-	pn.GET("/roles/:subject", h.GetRolesForUser)
-	pn.GET("/:subject/:object/:action", h.HasRoleForUser)
+	pn := apiv1.Group("/permissions", jwt.MiddlewareFunc(), rbac)
+	pn.GET("", h.GetPermissions(r))
 
 	// Security Group
 	sg := apiv1.Group("/security-groups")
@@ -127,12 +133,6 @@ func Init(verbose bool, publicPath string, service service.REST, enforcer *casbi
 	// Manager View
 	r.Use(static.Serve("/", static.LocalFile(publicPath, false)))
 
-	// Auto init roles and check roles
-	err = rbacbase.InitRole(enforcer, r)
-	if err != nil {
-		return nil, err
-	}
-
 	// Swagger
 	apiSeagger := ginSwagger.URL("/swagger/doc.json")
 	r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, apiSeagger))
diff --git a/manager/service/permission.go b/manager/service/permission.go
index 8e0af879..d53c6e5a 100644
--- a/manager/service/permission.go
+++ b/manager/service/permission.go
@@ -17,86 +17,100 @@
 package service
 
 import (
-	"fmt"
-	"strings"
+	"errors"
 
 	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/manager/model"
 	"d7y.io/dragonfly/v2/manager/permission/rbac"
 	"d7y.io/dragonfly/v2/manager/types"
-	"d7y.io/dragonfly/v2/pkg/util/stringutils"
 	"github.com/gin-gonic/gin"
 )
 
-func (s *rest) GetPermissionGroups(g *gin.Engine) types.PermissionGroups {
-	groups := rbac.GetAPIGroupNames(g)
-	if !stringutils.Contains(groups, "admin") {
-		groups = append(groups, "admin")
+func (s *rest) GetPermissions(g *gin.Engine) types.Permissions {
+	return rbac.GetAPIGroupNames(g)
+}
+
+func (s *rest) CreateRole(json types.CreateRolePermissionRequest) error {
+	for _, p := range json.Permissions {
+		res, err := s.enforcer.AddPolicy(json.RoleName, p.Object, p.Action)
+		if err != nil {
+			return err
+		}
+		if !res {
+			logger.Infof("The role %s that %s for %s already exist. skip!", json.RoleName, p.Object, p.Action)
+		}
+
 	}
-	return groups
+	return nil
 }
 
-func (s *rest) CreatePermission(json types.PolicyRequest) error {
-	roleName := rbac.RoleName(json.Object, json.Action)
-	res, err := s.enforcer.AddRoleForUser(json.Subject, roleName)
+func (s *rest) GetRoles() []string {
+	return s.enforcer.GetAllSubjects()
+}
+
+func (s *rest) AddRolePermission(roleName string, json types.ObjectPermission) error {
+	_, err := s.enforcer.AddPolicy(roleName, json.Object, json.Action)
 	if err != nil {
 		return err
 	}
-	if !res {
-		logger.Infof("The role %s of %s already exist. skip!", roleName, json.Subject)
-	}
+
 	return nil
 }
 
-func (s *rest) GetRolesForUser(subject string) ([]map[string]string, error) {
-	result := []map[string]string{}
-	policyToAction := map[string]string{
-		"read": "read",
-		"*":    "write",
-	}
-	res, err := s.enforcer.GetRolesForUser(subject)
+func (s *rest) RemoveRolePermission(roleName string, json types.ObjectPermission) error {
+	_, err := s.enforcer.RemovePolicy(roleName, json.Object, json.Action)
 	if err != nil {
-		return nil, err
-	}
-
-	for _, role := range res {
-		if role == "admin" {
-			result = append(result, map[string]string{"object": "admin", "description": "admin", "action": ""})
-		} else {
-			roleInfo := strings.Split(role, ":")
-			action := policyToAction[roleInfo[1]]
-			result = append(result, map[string]string{"object": roleInfo[0], "description": fmt.Sprintf("%s for %s", action, roleInfo[0]), "action": action})
-		}
+		return err
 	}
-
-	return result, nil
+	return nil
 }
 
-func (s *rest) HasRoleForUser(subject, object, action string) (bool, error) {
-	roleName := rbac.RoleName(object, action)
-	res, err := s.enforcer.HasRoleForUser(subject, roleName)
+func (s *rest) DestroyRole(roleName string) error {
+	_, err := s.enforcer.DeleteRole(roleName)
+	s.enforcer.GetAllRoles()
 	if err != nil {
-		return false, err
+		return err
 	}
-	if action == "read" {
-		writeRoleName := rbac.RoleName(object, "write")
-		writeRes, err := s.enforcer.HasRoleForUser(subject, writeRoleName)
-		if err != nil {
-			return false, err
-		}
-		return res || writeRes, nil
+	return nil
+}
 
+func (s *rest) GetRole(roleName string) []map[string]string {
+	result := []map[string]string{}
+	policies := s.enforcer.GetFilteredPolicy(0, roleName)
+	for _, p := range policies {
+		result = append(result, map[string]string{"object": p[1], "action": p[2]})
 	}
-	return res, nil
+	return result
 }
 
-func (s *rest) DestroyPermission(json types.PolicyRequest) error {
-	roleName := rbac.RoleName(json.Object, json.Action)
-	res, err := s.enforcer.DeleteRoleForUser(json.Subject, roleName)
-	if err != nil {
-		return err
+func (s *rest) GetRolesForUser(UserID uint, currentUserName string) ([]string, error) {
+	var results []string
+	var err error
+	user := model.User{}
+	if err := s.db.First(&user, UserID).Error; err != nil {
+		return nil, err
 	}
-	if !res {
-		logger.Infof("The role %s of %s already remove. skip!", roleName, json.Subject)
+	queryUserName := user.Name
+
+	if queryUserName == currentUserName {
+		results, err = s.enforcer.GetRolesForUser(queryUserName)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		has, err := s.enforcer.Enforce(currentUserName, "users", "read")
+		if err != nil {
+			return nil, err
+		}
+		if has {
+			results, err = s.enforcer.GetRolesForUser(queryUserName)
+			if err != nil {
+				return nil, err
+			}
+		} else {
+			return nil, errors.New("permission deny")
+		}
 	}
-	return nil
+
+	return results, nil
 }
diff --git a/manager/service/service.go b/manager/service/service.go
index db40ed3d..a4d7d863 100644
--- a/manager/service/service.go
+++ b/manager/service/service.go
@@ -33,11 +33,18 @@ type REST interface {
 	SignIn(json types.SignInRequest) (*model.User, error)
 	SignUp(json types.SignUpRequest) (*model.User, error)
 
-	GetPermissionGroups(g *gin.Engine) types.PermissionGroups
-	CreatePermission(json types.PolicyRequest) error
-	DestroyPermission(json types.PolicyRequest) error
-	GetRolesForUser(subject string) ([]map[string]string, error)
-	HasRoleForUser(subject, object, action string) (bool, error)
+	AddRoleForUser(uint, string) error
+	DeleteRoleForUser(uint, string) error
+
+	GetPermissions(*gin.Engine) types.Permissions
+
+	CreateRole(json types.CreateRolePermissionRequest) error
+	DestroyRole(string) error
+	AddRolePermission(string, types.ObjectPermission) error
+	RemoveRolePermission(string, types.ObjectPermission) error
+	GetRoles() []string
+	GetRole(string) []map[string]string
+	GetRolesForUser(uint, string) ([]string, error)
 
 	CreateCDNCluster(types.CreateCDNClusterRequest) (*model.CDNCluster, error)
 	CreateCDNClusterWithSecurityGroupDomain(types.CreateCDNClusterRequest) (*model.CDNCluster, error)
diff --git a/manager/service/user.go b/manager/service/user.go
index 1d77a0d9..e0c390bf 100644
--- a/manager/service/user.go
+++ b/manager/service/user.go
@@ -61,3 +61,27 @@ func (s *rest) SignUp(json types.SignUpRequest) (*model.User, error) {
 
 	return &user, nil
 }
+
+func (s *rest) AddRoleForUser(UserID uint, roleName string) error {
+	user := model.User{}
+	if err := s.db.First(&user, UserID).Error; err != nil {
+		return err
+	}
+	_, err := s.enforcer.AddRoleForUser(user.Name, roleName)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (s *rest) DeleteRoleForUser(UserID uint, roleName string) error {
+	user := model.User{}
+	if err := s.db.First(&user, UserID).Error; err != nil {
+		return err
+	}
+	_, err := s.enforcer.DeleteRoleForUser(user.Name, roleName)
+	if err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/manager/types/permission.go b/manager/types/permission.go
index 71193027..fad65c9f 100644
--- a/manager/types/permission.go
+++ b/manager/types/permission.go
@@ -16,20 +16,22 @@
 
 package types
 
-type PolicyRequest struct {
-	Subject string `form:"subject" binding:"required,min=1"`
-	Object  string `form:"object" binding:"required,min=1"`
-	Action  string `form:"aciton" binding:"omitempty,oneof=read write"`
+type RoleParams struct {
+	RoleName string `uri:"role_name" binding:"required,min=1"`
 }
 
-type PermissionGroups []string
+type UserParams struct {
+	ID uint `uri:"user_name" binding:"required"`
+}
 
-type UserRolesParams struct {
-	Subject string `uri:"subject" binding:"required"`
+type ObjectPermission struct {
+	Object string `json:"object" binding:"required,min=1"`
+	Action string `json:"action" binding:"required,oneof=read *"`
 }
 
-type UserHasRoleParams struct {
-	UserRolesParams
-	Object string `uri:"object" binding:"required"`
-	Action string `uri:"action" binding:"omitempty,oneof=read write"`
+type CreateRolePermissionRequest struct {
+	RoleName    string             `json:"role_name" binding:"required"`
+	Permissions []ObjectPermission `json:"permissions" binding:"dive"`
 }
+
+type Permissions []string
diff --git a/manager/types/user.go b/manager/types/user.go
index 48f167d6..f188123b 100644
--- a/manager/types/user.go
+++ b/manager/types/user.go
@@ -29,3 +29,8 @@ type SignUpRequest struct {
 	Location string `form:"location" binding:"omitempty"`
 	BIO      string `form:"bio" binding:"omitempty"`
 }
+
+type RoleRequest struct {
+	RoleName string `uri:"role_name" binding:"required,min=1"`
+	ID       uint   `uri:"id" binding:"required,min=1"`
+}


Test patch:
diff --git a/manager/permission/rbac/rbac_test.go b/manager/permission/rbac/rbac_test.go
index f5902ba9..8719c32d 100644
--- a/manager/permission/rbac/rbac_test.go
+++ b/manager/permission/rbac/rbac_test.go
@@ -91,7 +91,7 @@ func TestRoleName(t *testing.T) {
 		},
 		{
 			object:           "cdns",
-			action:           "write",
+			action:           "*",
 			exceptedRoleName: "cdns:*",
 		},
 	}