diff --git a/manager/database/database.go b/manager/database/database.go index 4bbd310d4..1b7be26b0 100644 --- a/manager/database/database.go +++ b/manager/database/database.go @@ -158,20 +158,21 @@ func seed(db *gorm.DB) error { } } - var adminUserCount int64 - var adminUserName = "admin" - if err := db.Model(model.User{}).Where("name = ?", adminUserName).Count(&adminUserCount).Error; err != nil { + var rootUserCount int64 + if err := db.Model(model.User{}).Count(&rootUserCount).Error; err != nil { return err } - if adminUserCount <= 0 { - encryptedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte("Dragonfly2"), bcrypt.MinCost) + if rootUserCount <= 0 { + encryptedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte("dragonfly"), bcrypt.MinCost) if err != nil { return err } if err := db.Create(&model.User{ + Model: model.Model{ + ID: uint(1), + }, EncryptedPassword: string(encryptedPasswordBytes), - Name: adminUserName, - Email: fmt.Sprintf("%s@Dragonfly2.com", adminUserName), + Name: "root", State: model.UserStateEnabled, }).Error; err != nil { return err diff --git a/manager/handlers/permission.go b/manager/handlers/permission.go index d65a9ee8b..d12d262da 100644 --- a/manager/handlers/permission.go +++ b/manager/handlers/permission.go @@ -19,7 +19,6 @@ package handlers import ( "net/http" - "d7y.io/dragonfly/v2/manager/types" "github.com/gin-gonic/gin" ) @@ -31,154 +30,9 @@ import ( // @Failure 400 {object} HTTPError // @Failure 500 {object} HTTPError // @Router /permissions [get] - func (h *Handlers) GetPermissions(g *gin.Engine) func(ctx *gin.Context) { return func(ctx *gin.Context) { - - permissionGroups := h.Service.GetPermissions(g) - - ctx.JSON(http.StatusOK, permissionGroups) + permissions := h.Service.GetPermissions(g) + ctx.JSON(http.StatusOK, permissions) } } - -// @Summary Create Role -// @Description Create Role by json config -// @Tags role -// @Accept json -// @Produce json -// @Success 200 -// @Failure 400 {object} HTTPError -// @Failure 500 {object} HTTPError -// @Router /roles [post] - -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 - } - err := h.Service.CreateRole(json) - if err != nil { - ctx.Error(err) - return - } - ctx.Status(http.StatusOK) -} - -// @Summary Update Role -// @Description Remove Role Permission by json config -// @Tags role -// @Accept json -// @Produce json -// @Success 200 -// @Failure 400 {object} HTTPError -// @Failure 500 {object} HTTPError -// @Router /roles/:role_name/permission [delete] - -func (h *Handlers) RemoveRolePermission(ctx *gin.Context) { - - var params types.RoleParams - if err := ctx.ShouldBindUri(¶ms); 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.RemoveRolePermission(params.RoleName, json) - if err != nil { - ctx.Error(err) - return - } - ctx.Status(http.StatusOK) -} - -// @Summary Update Role -// @Description Add Role Permission by json config -// @Tags role -// @Accept json -// @Produce json -// @Success 200 -// @Failure 400 {object} HTTPError -// @Failure 500 {object} HTTPError -// @Router /roles/:role_name/permission [post] - -func (h *Handlers) AddRolePermission(ctx *gin.Context) { - - var params types.RoleParams - if err := ctx.ShouldBindUri(¶ms); 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.AddRolePermission(params.RoleName, json) - if err != nil { - ctx.Error(err) - return - } - 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(¶ms); 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 {object} HTTPError -// @Failure 500 {object} HTTPError -// @Router /role/:role_name [delete] - -func (h *Handlers) DestroyRole(ctx *gin.Context) { - var params types.RoleParams - if err := ctx.ShouldBindUri(¶ms); err != nil { - ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) - return - } - err := h.Service.DestroyRole(params.RoleName) - if err != nil { - ctx.Error(err) - return - } - ctx.Status(http.StatusOK) -} diff --git a/manager/handlers/role.go b/manager/handlers/role.go new file mode 100644 index 000000000..3425364f5 --- /dev/null +++ b/manager/handlers/role.go @@ -0,0 +1,179 @@ +/* + * Copyright 2020 The Dragonfly Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package handlers + +import ( + "net/http" + + "d7y.io/dragonfly/v2/manager/types" + "github.com/gin-gonic/gin" +) + +// @Summary Create Role +// @Description Create Role by json config +// @Tags role +// @Accept json +// @Produce json +// @Success 200 +// @Failure 400 {object} HTTPError +// @Failure 500 {object} HTTPError +// @Router /roles [post] +func (h *Handlers) CreateRole(ctx *gin.Context) { + var json types.CreateRoleRequest + if err := ctx.ShouldBindJSON(&json); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + if err := h.Service.CreateRole(json); err != nil { + ctx.Error(err) + return + } + + ctx.Status(http.StatusOK) +} + +// @Summary Destroy Role +// @Description Destroy role by json config +// @Tags permission +// @Accept json +// @Produce json +// @Param role path string true "role" +// @Success 200 +// @Failure 400 {object} HTTPError +// @Failure 500 {object} HTTPError +// @Router /role/:role [delete] +func (h *Handlers) DestroyRole(ctx *gin.Context) { + var params types.RoleParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + if ok, err := h.Service.DestroyRole(params.Role); err != nil { + ctx.Error(err) + return + } else if !ok { + ctx.Status(http.StatusNotFound) + return + } + + ctx.Status(http.StatusOK) +} + +// @Summary Get Role +// @Description Get Role +// @Tags permission +// @Accept json +// @Produce json +// @Success 200 +// @Failure 400 {object} HTTPError +// @Failure 500 {object} HTTPError +// @Router /roles/:role [get] +func (h *Handlers) GetRole(ctx *gin.Context) { + var params types.RoleParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + ctx.JSON(http.StatusOK, h.Service.GetRole(params.Role)) +} + +// @Summary Get Roles +// @Description Get roles +// @Tags role +// @Accept json +// @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 Add Permission For Role +// @Description Add Permission by json config +// @Tags role +// @Accept json +// @Produce json +// @Param Permission body types.AddPermissionForRoleRequest true "Permission" +// @Param role path string true "role" +// @Success 200 +// @Failure 400 {object} HTTPError +// @Failure 500 {object} HTTPError +// @Router /roles/:role/permissions [post] +func (h *Handlers) AddPermissionForRole(ctx *gin.Context) { + var params types.RoleParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + var json types.AddPermissionForRoleRequest + if err := ctx.ShouldBindJSON(&json); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + if ok, err := h.Service.AddPermissionForRole(params.Role, json); err != nil { + ctx.Error(err) + return + } else if !ok { + ctx.Status(http.StatusConflict) + return + } + + ctx.Status(http.StatusOK) +} + +// @Summary Update Role +// @Description Remove Role Permission by json config +// @Tags role +// @Accept json +// @Produce json +// @Param Permission body types.DeletePermissionForRoleRequest true "Permission" +// @Param role path string true "role" +// @Success 200 +// @Failure 400 {object} HTTPError +// @Failure 500 {object} HTTPError +// @Router /roles/:role/permissions [delete] +func (h *Handlers) DeletePermissionForRole(ctx *gin.Context) { + var params types.RoleParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + var json types.DeletePermissionForRoleRequest + if err := ctx.ShouldBindJSON(&json); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + if ok, err := h.Service.DeletePermissionForRole(params.Role, json); err != nil { + ctx.Error(err) + return + } else if !ok { + ctx.Status(http.StatusNotFound) + return + } + + ctx.Status(http.StatusOK) +} diff --git a/manager/handlers/user.go b/manager/handlers/user.go index 15c5b86f1..6ac9cd724 100644 --- a/manager/handlers/user.go +++ b/manager/handlers/user.go @@ -58,63 +58,21 @@ func (h *Handlers) SignUp(ctx *gin.Context) { // @Success 200 // @Failure 400 // @Failure 500 -// @Router /users/reset_password [post] +// @Router /users/:id/reset_password [post] func (h *Handlers) ResetPassword(ctx *gin.Context) { + var params types.UserParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + var json types.ResetPasswordRequest if err := ctx.ShouldBindJSON(&json); err != nil { ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) return } - if err := h.Service.ResetPassword(ctx.GetUint("userID"), json); err != nil { - ctx.Error(err) - return - } - - ctx.Status(http.StatusOK) -} - -// @Summary Delete Role For User -// @Description delete role 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.DeleteRoleForUserParams - if err := ctx.ShouldBindUri(¶ms); err != nil { - ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) - return - } - - if err := h.Service.DeleteRoleForUser(params); err != nil { - ctx.Error(err) - return - } - - ctx.Status(http.StatusOK) -} - -// @Summary Add Role For User -// @Description add role to 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.AddRoleForUserParams - if err := ctx.ShouldBindUri(¶ms); err != nil { - ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) - return - } - - if err := h.Service.AddRoleForUser(params); err != nil { + if err := h.Service.ResetPassword(params.ID, json); err != nil { ctx.Error(err) return } @@ -126,7 +84,8 @@ func (h *Handlers) AddRoleToUser(ctx *gin.Context) { // @Description get roles by json config // @Tags User // @Produce json -// @Success 200 {object} RoutesInfo +// @Param id path string true "id" +// @Success 200 {object} []string // @Failure 400 {object} HTTPError // @Failure 500 {object} HTTPError // @Router /users/:id/roles [get] @@ -137,7 +96,7 @@ func (h *Handlers) GetRolesForUser(ctx *gin.Context) { return } - roles, err := h.Service.GetRolesForUser(params.ID, ctx.GetString("userName")) + roles, err := h.Service.GetRolesForUser(params.ID) if err != nil { ctx.Error(err) return @@ -145,3 +104,61 @@ func (h *Handlers) GetRolesForUser(ctx *gin.Context) { ctx.JSON(http.StatusOK, roles) } + +// @Summary Add Role For User +// @Description add role to user by uri config +// @Tags users +// @Accept text +// @Produce json +// @Param id path string true "id" +// @Param role path string true "role" +// @Success 200 +// @Failure 400 {object} HTTPError +// @Failure 500 {object} HTTPError +// @Router /users/:id/roles/:role [put] +func (h *Handlers) AddRoleToUser(ctx *gin.Context) { + var params types.AddRoleForUserParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + if ok, err := h.Service.AddRoleForUser(params); err != nil { + ctx.Error(err) + return + } else if !ok { + ctx.Status(http.StatusConflict) + return + } + + ctx.Status(http.StatusOK) +} + +// @Summary Delete Role For User +// @Description delete role by uri config +// @Tags users +// @Accept text +// @Produce json +// @Param id path string true "id" +// @Param role path string true "role" +// @Success 200 +// @Failure 400 {object} HTTPError +// @Failure 500 {object} HTTPError +// @Router /users/:id/roles/:role [delete] +func (h *Handlers) DeleteRoleForUser(ctx *gin.Context) { + var params types.DeleteRoleForUserParams + if err := ctx.ShouldBindUri(¶ms); err != nil { + ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) + return + } + + if ok, err := h.Service.DeleteRoleForUser(params); err != nil { + ctx.Error(err) + return + } else if !ok { + ctx.Status(http.StatusNotFound) + return + } + + ctx.Status(http.StatusOK) +} diff --git a/manager/manager.go b/manager/manager.go index e02c1a109..62d32115c 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -93,7 +93,7 @@ func New(cfg *config.Config) (*Server, error) { } // Initialize roles and check roles - err = rbac.InitRole(enforcer, router) + err = rbac.InitRBAC(enforcer, router) if err != nil { return nil, err } diff --git a/manager/middlewares/jwt.go b/manager/middlewares/jwt.go index 5be34da89..8302cf3cf 100644 --- a/manager/middlewares/jwt.go +++ b/manager/middlewares/jwt.go @@ -28,12 +28,13 @@ import ( ) type user struct { - userName string - ID uint + name string + id uint } func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) { - var identityKey = "username" + identityKey := "id" + authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{ Realm: "Dragonfly", Key: []byte("Secret Key"), @@ -44,7 +45,7 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) { IdentityHandler: func(c *gin.Context) interface{} { claims := jwt.ExtractClaims(c) - userName, ok := claims[identityKey] + id, ok := claims[identityKey] if !ok { c.JSON(http.StatusUnauthorized, gin.H{ "message": "Unavailable token: require user name", @@ -53,7 +54,7 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) { return nil } - userID, ok := claims["ID"] + name, ok := claims["name"] if !ok { c.JSON(http.StatusUnauthorized, gin.H{ "message": "Unavailable token: require user id", @@ -63,12 +64,12 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) { } u := &user{ - userName: userName.(string), - ID: userID.(uint), + name: name.(string), + id: id.(uint), } - c.Set("userName", u.userName) - c.Set("userID", u.ID) + c.Set("name", u.name) + c.Set("id", u.id) return u }, @@ -89,8 +90,8 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) { PayloadFunc: func(data interface{}) jwt.MapClaims { if u, ok := data.(*model.User); ok { return jwt.MapClaims{ - identityKey: u.Name, - "ID": u.ID, + identityKey: u.ID, + "name": u.Name, } } diff --git a/manager/middlewares/rbac.go b/manager/middlewares/rbac.go index 5c47bbc06..d1543fcef 100644 --- a/manager/middlewares/rbac.go +++ b/manager/middlewares/rbac.go @@ -17,6 +17,7 @@ package middlewares import ( + "fmt" "net/http" logger "d7y.io/dragonfly/v2/internal/dflog" @@ -27,47 +28,35 @@ import ( func RBAC(e *casbin.Enforcer) gin.HandlerFunc { return func(c *gin.Context) { - userName := c.GetString("userName") - // request path - p := c.Request.URL.Path - permissionName, err := rbac.GetAPIGroupName(p) - if err != nil { - c.Next() - return - } - // request method - m := c.Request.Method - action := rbac.HTTPMethodToAction(m) - // rbac validation - adminRes, err := e.HasRoleForUser(userName, "admin") + action := rbac.HTTPMethodToAction(c.Request.Method) + permission, err := rbac.GetAPIGroupName(c.Request.URL.Path) if err != nil { + logger.Errorf("get api group name error: %s", err) c.JSON(http.StatusUnauthorized, gin.H{ - "message": "permission validate error", + "message": "permission validate error!", }) c.Abort() return } - if adminRes { - c.Next() - return - } - res, err := e.Enforce(userName, permissionName, action) + + ok, err := e.Enforce(fmt.Sprint(c.GetUint("id")), permission, action) if err != nil { logger.Errorf("RBAC validate error: %s", err) c.JSON(http.StatusUnauthorized, gin.H{ - "message": "permission validate error, please see log!", + "message": "permission validate error!", }) c.Abort() return } - if !res { + + if !ok { c.JSON(http.StatusUnauthorized, gin.H{ "message": "permission deny", }) c.Abort() return } - c.Next() + c.Next() } } diff --git a/manager/permission/rbac/rbac.go b/manager/permission/rbac/rbac.go index a7b47a285..7534a9235 100644 --- a/manager/permission/rbac/rbac.go +++ b/manager/permission/rbac/rbac.go @@ -18,12 +18,9 @@ package rbac import ( "errors" - "fmt" "net/http" "regexp" - "strings" - logger "d7y.io/dragonfly/v2/internal/dflog" managermodel "d7y.io/dragonfly/v2/manager/model" "d7y.io/dragonfly/v2/pkg/util/stringutils" "github.com/casbin/casbin/v2" @@ -56,93 +53,83 @@ func NewEnforcer(gdb *gorm.DB) (*casbin.Enforcer, error) { if err != nil { return nil, err } + m, err := model.NewModelFromString(modelText) if err != nil { return nil, err } + enforcer, err := casbin.NewEnforcer(m, adapter) if err != nil { return nil, err } + return enforcer, nil } -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]) - if err != nil { - return err - } - - // init admin permissions - _, err = e.AddPolicy("admin", roleInfo[0], "*") - if err != nil { +func InitRBAC(e *casbin.Enforcer, g *gin.Engine) error { + permissions := GetPermissions(g) + for _, permission := range permissions { + if _, err := e.AddPermissionForUser("root", permission.Object, "*"); err != nil { return err } } - if _, err := e.AddRoleForUser("admin", "admin"); err != nil { + + if _, err := e.AddRoleForUser("1", "root"); err != nil { return err } - logger.Info("init and check role success") - return nil + return nil +} + +type Permission struct { + Object string + Action string +} + +func GetPermissions(g *gin.Engine) []Permission { + permissions := []Permission{} + actions := []string{"read", "*"} + for _, permission := range GetAPIGroupNames(g) { + for _, action := range actions { + permissions = append(permissions, Permission{ + Object: permission, + Action: action, + }) + } + } + + return permissions +} + +func GetAPIGroupNames(g *gin.Engine) []string { + apiGroupNames := []string{} + for _, route := range g.Routes() { + name, err := GetAPIGroupName(route.Path) + if err != nil { + continue + } + + if !stringutils.Contains(apiGroupNames, name) { + apiGroupNames = append(apiGroupNames, name) + } + } + + return apiGroupNames } func GetAPIGroupName(path string) (string, error) { apiGroupRegexp := regexp.MustCompile(`^/api/v[0-9]+/([-_a-zA-Z]*)[/.*]*`) matchs := apiGroupRegexp.FindStringSubmatch(path) if len(matchs) != 2 { - return "", errors.New("faild to find api group") + return "", errors.New("cannot find group name") } return matchs[1], nil } -func RoleName(object, action string) string { - if object == "admin" { - return "admin" - } - return fmt.Sprintf("%s:%s", object, action) -} - -func GetAPIGroupNames(g *gin.Engine) []string { - APIGroups := []string{} - for _, route := range g.Routes() { - apiGroupName, err := GetAPIGroupName(route.Path) - if err != nil { - continue - } - if !stringutils.Contains(APIGroups, apiGroupName) { - APIGroups = append(APIGroups, apiGroupName) - } - - } - - return APIGroups -} - -func SystemRoles(g *gin.Engine) []string { - Roles := []string{} - policyKeys := []string{"read", "*"} - - for _, apiGroup := range GetAPIGroupNames(g) { - for _, p := range policyKeys { - if !stringutils.Contains(Roles, apiGroup+":"+p) { - Roles = append(Roles, apiGroup+":"+p) - } - - } - } - return Roles -} - func HTTPMethodToAction(method string) string { action := "read" - if method == http.MethodDelete || method == http.MethodPatch || method == http.MethodPut || method == http.MethodPost { action = "*" } diff --git a/manager/permission/rbac/rbac_test.go b/manager/permission/rbac/rbac_test.go index 8719c32d7..9726e486f 100644 --- a/manager/permission/rbac/rbac_test.go +++ b/manager/permission/rbac/rbac_test.go @@ -57,7 +57,7 @@ func TestGetApiGroupName(t *testing.T) { path: "/api/user", expect: func(t *testing.T, data string, err error) { assert := assert.New(t) - assert.EqualError(err, "faild to find api group") + assert.EqualError(err, "cannot find group name") }, }, { @@ -65,7 +65,7 @@ func TestGetApiGroupName(t *testing.T) { path: "", expect: func(t *testing.T, data string, err error) { assert := assert.New(t) - assert.EqualError(err, "faild to find api group") + assert.EqualError(err, "cannot find group name") }, }, } @@ -78,32 +78,6 @@ func TestGetApiGroupName(t *testing.T) { } } -func TestRoleName(t *testing.T) { - tests := []struct { - object string - action string - exceptedRoleName string - }{ - { - object: "users", - action: "read", - exceptedRoleName: "users:read", - }, - { - object: "cdns", - action: "*", - exceptedRoleName: "cdns:*", - }, - } - - for _, tt := range tests { - roleName := RoleName(tt.object, tt.action) - if roleName != tt.exceptedRoleName { - t.Errorf("RoleName(%v, %v) = %v, want %v", tt.object, tt.action, roleName, tt.exceptedRoleName) - } - } - -} func TestHTTPMethodToAction(t *testing.T) { tests := []struct { method string @@ -129,5 +103,4 @@ func TestHTTPMethodToAction(t *testing.T) { t.Errorf("HttpMethodToAction(%v) = %v, want %v", tt.method, action, tt.exceptedAction) } } - } diff --git a/manager/router/router.go b/manager/router/router.go index b021aea3a..245bc54dc 100644 --- a/manager/router/router.go +++ b/manager/router/router.go @@ -79,14 +79,38 @@ func Init(console bool, verbose bool, publicPath string, service service.REST, e apiv1 := r.Group("/api/v1") // User - ai := apiv1.Group("/users") - ai.POST("/signin", jwt.LoginHandler) - ai.POST("/signout", jwt.LogoutHandler) - ai.POST("/signup", h.SignUp) - ai.POST("/refresh_token", jwt.RefreshHandler) - ai.POST("/reset_password", h.ResetPassword) - ai.POST("/:id/roles/:role_name", h.AddRoleToUser, jwt.MiddlewareFunc(), rbac) - ai.DELETE("/:id/roles/:role_name", h.DeleteRoleForUser, jwt.MiddlewareFunc(), rbac) + u := apiv1.Group("/users") + u.POST("/signin", jwt.LoginHandler) + u.POST("/signout", jwt.LogoutHandler) + u.POST("/signup", h.SignUp) + u.POST("/refresh_token", jwt.RefreshHandler) + u.POST("/:id/reset_password", h.ResetPassword) + u.GET("/:id/roles", h.GetRolesForUser) + u.PUT("/:id/roles/:role", h.AddRoleToUser) + u.DELETE("/:id/roles/:role", h.DeleteRoleForUser) + + // Role + re := apiv1.Group("/roles") + re.POST("", h.CreateRole) + re.DELETE("/:role", h.DestroyRole) + re.GET("/:role", h.GetRole) + re.GET("", h.GetRoles) + re.POST("/:role/permissions", h.AddPermissionForRole) + re.DELETE("/:role/permissions", h.DeletePermissionForRole) + + // Permission + pm := apiv1.Group("/permissions", jwt.MiddlewareFunc(), rbac) + pm.GET("", h.GetPermissions(r)) + + // Oauth + oa := apiv1.Group("/oauth") + oa.GET("", h.GetOauths) + oa.GET("/:id", h.GetOauth) + oa.DELETE("/:id", h.DestroyOauth) + oa.PUT("/:id", h.UpdateOauth) + oa.POST("", h.CreateOauth) + oa.GET("/signin/:oauth_name", h.OauthSignin) + oa.GET("/callback/:oauth_name", h.OauthCallback(jwt)) // Scheduler Cluster sc := apiv1.Group("/scheduler-clusters") @@ -130,29 +154,6 @@ func Init(console bool, verbose bool, publicPath string, service service.REST, e c.GET(":id", h.GetCDN) c.GET("", h.GetCDNs) - // Role - 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("/permissions", jwt.MiddlewareFunc(), rbac) - pn.GET("", h.GetPermissions(r)) - - // oauth - oa := apiv1.Group("/oauth") - oa.GET("", h.GetOauths) - oa.GET("/:id", h.GetOauth) - oa.DELETE("/:id", h.DestroyOauth) - oa.PUT("/:id", h.UpdateOauth) - oa.POST("", h.CreateOauth) - oa.GET("/signin/:oauth_name", h.OauthSignin) - oa.GET("/callback/:oauth_name", h.OauthCallback(jwt)) - // Security Group sg := apiv1.Group("/security-groups") sg.POST("", h.CreateSecurityGroup) diff --git a/manager/service/permission.go b/manager/service/permission.go index d53c6e5ab..04f9e50fe 100644 --- a/manager/service/permission.go +++ b/manager/service/permission.go @@ -17,100 +17,10 @@ package service import ( - "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" "github.com/gin-gonic/gin" ) -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 nil -} - -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 - } - - return nil -} - -func (s *rest) RemoveRolePermission(roleName string, json types.ObjectPermission) error { - _, err := s.enforcer.RemovePolicy(roleName, json.Object, json.Action) - if err != nil { - return err - } - return nil -} - -func (s *rest) DestroyRole(roleName string) error { - _, err := s.enforcer.DeleteRole(roleName) - s.enforcer.GetAllRoles() - if err != nil { - return err - } - 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 result -} - -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 - } - 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 results, nil +func (s *rest) GetPermissions(g *gin.Engine) []rbac.Permission { + return rbac.GetPermissions(g) } diff --git a/manager/service/role.go b/manager/service/role.go new file mode 100644 index 000000000..e6d799acf --- /dev/null +++ b/manager/service/role.go @@ -0,0 +1,52 @@ +/* + * Copyright 2020 The Dragonfly Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package service + +import ( + "d7y.io/dragonfly/v2/manager/types" +) + +func (s *rest) CreateRole(json types.CreateRoleRequest) error { + for _, permission := range json.Permissions { + _, err := s.enforcer.AddPermissionForUser(json.Role, permission.Object, permission.Action) + if err != nil { + return err + } + } + + return nil +} + +func (s *rest) DestroyRole(role string) (bool, error) { + return s.enforcer.DeleteRole(role) +} + +func (s *rest) GetRole(role string) [][]string { + return s.enforcer.GetPermissionsForUser(role) +} + +func (s *rest) GetRoles() []string { + return s.enforcer.GetAllRoles() +} + +func (s *rest) AddPermissionForRole(role string, json types.AddPermissionForRoleRequest) (bool, error) { + return s.enforcer.AddPermissionForUser(role, json.Object, json.Action) +} + +func (s *rest) DeletePermissionForRole(role string, json types.DeletePermissionForRoleRequest) (bool, error) { + return s.enforcer.DeletePermissionForUser(role, json.Object, json.Action) +} diff --git a/manager/service/service.go b/manager/service/service.go index 34788e540..023f5b657 100644 --- a/manager/service/service.go +++ b/manager/service/service.go @@ -21,6 +21,7 @@ import ( "d7y.io/dragonfly/v2/manager/database" "d7y.io/dragonfly/v2/manager/job" "d7y.io/dragonfly/v2/manager/model" + "d7y.io/dragonfly/v2/manager/permission/rbac" "d7y.io/dragonfly/v2/manager/types" "github.com/casbin/casbin/v2" "github.com/gin-gonic/gin" @@ -33,19 +34,18 @@ type REST interface { SignIn(types.SignInRequest) (*model.User, error) SignUp(types.SignUpRequest) (*model.User, error) ResetPassword(uint, types.ResetPasswordRequest) error + GetRolesForUser(uint) ([]string, error) + AddRoleForUser(types.AddRoleForUserParams) (bool, error) + DeleteRoleForUser(types.DeleteRoleForUserParams) (bool, error) - AddRoleForUser(types.AddRoleForUserParams) error - DeleteRoleForUser(types.DeleteRoleForUserParams) error - - GetPermissions(*gin.Engine) types.Permissions - - CreateRole(json types.CreateRolePermissionRequest) error - DestroyRole(string) error - AddRolePermission(string, types.ObjectPermission) error - RemoveRolePermission(string, types.ObjectPermission) error + CreateRole(json types.CreateRoleRequest) error + DestroyRole(string) (bool, error) + GetRole(string) [][]string GetRoles() []string - GetRole(string) []map[string]string - GetRolesForUser(uint, string) ([]string, error) + AddPermissionForRole(string, types.AddPermissionForRoleRequest) (bool, error) + DeletePermissionForRole(string, types.DeletePermissionForRoleRequest) (bool, error) + + GetPermissions(*gin.Engine) []rbac.Permission 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 20b390a5f..910d37b23 100644 --- a/manager/service/user.go +++ b/manager/service/user.go @@ -88,18 +88,14 @@ func (s *rest) SignUp(json types.SignUpRequest) (*model.User, error) { return &user, nil } -func (s *rest) AddRoleForUser(json types.AddRoleForUserParams) error { - if _, err := s.enforcer.AddRoleForUser(fmt.Sprint(json.ID), json.RoleName); err != nil { - return err - } - - return nil +func (s *rest) GetRolesForUser(id uint) ([]string, error) { + return s.enforcer.GetRolesForUser(fmt.Sprint(id)) } -func (s *rest) DeleteRoleForUser(json types.DeleteRoleForUserParams) error { - if _, err := s.enforcer.DeleteRoleForUser(fmt.Sprint(json.ID), json.RoleName); err != nil { - return err - } - - return nil +func (s *rest) AddRoleForUser(json types.AddRoleForUserParams) (bool, error) { + return s.enforcer.AddRoleForUser(fmt.Sprint(json.ID), json.Role) +} + +func (s *rest) DeleteRoleForUser(json types.DeleteRoleForUserParams) (bool, error) { + return s.enforcer.DeleteRoleForUser(fmt.Sprint(json.ID), json.Role) } diff --git a/manager/types/permission.go b/manager/types/role.go similarity index 57% rename from manager/types/permission.go rename to manager/types/role.go index fad65c9f9..6d7871b41 100644 --- a/manager/types/permission.go +++ b/manager/types/role.go @@ -16,22 +16,23 @@ package types +import ( + "d7y.io/dragonfly/v2/manager/permission/rbac" +) + +type CreateRoleRequest struct { + Role string `json:"role" binding:"required"` + Permissions []rbac.Permission `json:"permissions" binding:"required"` +} + type RoleParams struct { - RoleName string `uri:"role_name" binding:"required,min=1"` + Role string `uri:"role" binding:"required"` } -type UserParams struct { - ID uint `uri:"user_name" binding:"required"` +type AddPermissionForRoleRequest struct { + rbac.Permission `json:",inline" binding:"required"` } -type ObjectPermission struct { - Object string `json:"object" binding:"required,min=1"` - Action string `json:"action" binding:"required,oneof=read *"` +type DeletePermissionForRoleRequest struct { + rbac.Permission `json:",inline" binding:"required"` } - -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 71645d25a..4e8d04771 100644 --- a/manager/types/user.go +++ b/manager/types/user.go @@ -16,6 +16,10 @@ package types +type UserParams struct { + ID uint `uri:"id" binding:"required"` +} + type SignInRequest struct { Name string `json:"name" binding:"required,min=3,max=10"` Password string `json:"password" binding:"required,min=8,max=20"` @@ -36,11 +40,11 @@ type SignUpRequest struct { } type DeleteRoleForUserParams struct { - ID uint `uri:"id" binding:"required"` - RoleName string `uri:"role_name" binding:"required,min=1"` + ID uint `uri:"id" binding:"required"` + Role string `uri:"role" binding:"required,min=1"` } type AddRoleForUserParams struct { - ID uint `uri:"id" binding:"required"` - RoleName string `uri:"role_name" binding:"required,min=1"` + ID uint `uri:"id" binding:"required"` + Role string `uri:"role" binding:"required,min=1"` }