refactor: rbac

Signed-off-by: Gaius <gaius.qi@gmail.com>
This commit is contained in:
Gaius 2021-08-30 18:39:09 +08:00
parent 11c0f4f8de
commit e52b72990b
No known key found for this signature in database
GPG Key ID: 8B4E5D1290FA2FFB
16 changed files with 458 additions and 493 deletions

View File

@ -158,20 +158,21 @@ func seed(db *gorm.DB) error {
} }
} }
var adminUserCount int64 var rootUserCount int64
var adminUserName = "admin" if err := db.Model(model.User{}).Count(&rootUserCount).Error; err != nil {
if err := db.Model(model.User{}).Where("name = ?", adminUserName).Count(&adminUserCount).Error; err != nil {
return err return err
} }
if adminUserCount <= 0 { if rootUserCount <= 0 {
encryptedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte("Dragonfly2"), bcrypt.MinCost) encryptedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte("dragonfly"), bcrypt.MinCost)
if err != nil { if err != nil {
return err return err
} }
if err := db.Create(&model.User{ if err := db.Create(&model.User{
Model: model.Model{
ID: uint(1),
},
EncryptedPassword: string(encryptedPasswordBytes), EncryptedPassword: string(encryptedPasswordBytes),
Name: adminUserName, Name: "root",
Email: fmt.Sprintf("%s@Dragonfly2.com", adminUserName),
State: model.UserStateEnabled, State: model.UserStateEnabled,
}).Error; err != nil { }).Error; err != nil {
return err return err

View File

@ -19,7 +19,6 @@ package handlers
import ( import (
"net/http" "net/http"
"d7y.io/dragonfly/v2/manager/types"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -31,154 +30,9 @@ import (
// @Failure 400 {object} HTTPError // @Failure 400 {object} HTTPError
// @Failure 500 {object} HTTPError // @Failure 500 {object} HTTPError
// @Router /permissions [get] // @Router /permissions [get]
func (h *Handlers) GetPermissions(g *gin.Engine) func(ctx *gin.Context) { func (h *Handlers) GetPermissions(g *gin.Engine) func(ctx *gin.Context) {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
permissions := h.Service.GetPermissions(g)
permissionGroups := h.Service.GetPermissions(g) ctx.JSON(http.StatusOK, permissions)
ctx.JSON(http.StatusOK, permissionGroups)
} }
} }
// @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(&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.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(&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.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(&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 {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(&params); 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)
}

179
manager/handlers/role.go Normal file
View File

@ -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(&params); 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(&params); 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(&params); 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(&params); 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)
}

View File

@ -58,63 +58,21 @@ func (h *Handlers) SignUp(ctx *gin.Context) {
// @Success 200 // @Success 200
// @Failure 400 // @Failure 400
// @Failure 500 // @Failure 500
// @Router /users/reset_password [post] // @Router /users/:id/reset_password [post]
func (h *Handlers) ResetPassword(ctx *gin.Context) { func (h *Handlers) ResetPassword(ctx *gin.Context) {
var params types.UserParams
if err := ctx.ShouldBindUri(&params); err != nil {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
return
}
var json types.ResetPasswordRequest var json types.ResetPasswordRequest
if err := ctx.ShouldBindJSON(&json); err != nil { if err := ctx.ShouldBindJSON(&json); err != nil {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()}) ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
return return
} }
if err := h.Service.ResetPassword(ctx.GetUint("userID"), json); err != nil { if err := h.Service.ResetPassword(params.ID, 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(&params); 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(&params); err != nil {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
return
}
if err := h.Service.AddRoleForUser(params); err != nil {
ctx.Error(err) ctx.Error(err)
return return
} }
@ -126,7 +84,8 @@ func (h *Handlers) AddRoleToUser(ctx *gin.Context) {
// @Description get roles by json config // @Description get roles by json config
// @Tags User // @Tags User
// @Produce json // @Produce json
// @Success 200 {object} RoutesInfo // @Param id path string true "id"
// @Success 200 {object} []string
// @Failure 400 {object} HTTPError // @Failure 400 {object} HTTPError
// @Failure 500 {object} HTTPError // @Failure 500 {object} HTTPError
// @Router /users/:id/roles [get] // @Router /users/:id/roles [get]
@ -137,7 +96,7 @@ func (h *Handlers) GetRolesForUser(ctx *gin.Context) {
return return
} }
roles, err := h.Service.GetRolesForUser(params.ID, ctx.GetString("userName")) roles, err := h.Service.GetRolesForUser(params.ID)
if err != nil { if err != nil {
ctx.Error(err) ctx.Error(err)
return return
@ -145,3 +104,61 @@ func (h *Handlers) GetRolesForUser(ctx *gin.Context) {
ctx.JSON(http.StatusOK, roles) 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(&params); 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(&params); 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)
}

View File

@ -93,7 +93,7 @@ func New(cfg *config.Config) (*Server, error) {
} }
// Initialize roles and check roles // Initialize roles and check roles
err = rbac.InitRole(enforcer, router) err = rbac.InitRBAC(enforcer, router)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -28,12 +28,13 @@ import (
) )
type user struct { type user struct {
userName string name string
ID uint id uint
} }
func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) { func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
var identityKey = "username" identityKey := "id"
authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{ authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
Realm: "Dragonfly", Realm: "Dragonfly",
Key: []byte("Secret Key"), Key: []byte("Secret Key"),
@ -44,7 +45,7 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
IdentityHandler: func(c *gin.Context) interface{} { IdentityHandler: func(c *gin.Context) interface{} {
claims := jwt.ExtractClaims(c) claims := jwt.ExtractClaims(c)
userName, ok := claims[identityKey] id, ok := claims[identityKey]
if !ok { if !ok {
c.JSON(http.StatusUnauthorized, gin.H{ c.JSON(http.StatusUnauthorized, gin.H{
"message": "Unavailable token: require user name", "message": "Unavailable token: require user name",
@ -53,7 +54,7 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
return nil return nil
} }
userID, ok := claims["ID"] name, ok := claims["name"]
if !ok { if !ok {
c.JSON(http.StatusUnauthorized, gin.H{ c.JSON(http.StatusUnauthorized, gin.H{
"message": "Unavailable token: require user id", "message": "Unavailable token: require user id",
@ -63,12 +64,12 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
} }
u := &user{ u := &user{
userName: userName.(string), name: name.(string),
ID: userID.(uint), id: id.(uint),
} }
c.Set("userName", u.userName) c.Set("name", u.name)
c.Set("userID", u.ID) c.Set("id", u.id)
return u return u
}, },
@ -89,8 +90,8 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
PayloadFunc: func(data interface{}) jwt.MapClaims { PayloadFunc: func(data interface{}) jwt.MapClaims {
if u, ok := data.(*model.User); ok { if u, ok := data.(*model.User); ok {
return jwt.MapClaims{ return jwt.MapClaims{
identityKey: u.Name, identityKey: u.ID,
"ID": u.ID, "name": u.Name,
} }
} }

View File

@ -17,6 +17,7 @@
package middlewares package middlewares
import ( import (
"fmt"
"net/http" "net/http"
logger "d7y.io/dragonfly/v2/internal/dflog" logger "d7y.io/dragonfly/v2/internal/dflog"
@ -27,47 +28,35 @@ import (
func RBAC(e *casbin.Enforcer) gin.HandlerFunc { func RBAC(e *casbin.Enforcer) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
userName := c.GetString("userName") action := rbac.HTTPMethodToAction(c.Request.Method)
// request path permission, err := rbac.GetAPIGroupName(c.Request.URL.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")
if err != nil { if err != nil {
logger.Errorf("get api group name error: %s", err)
c.JSON(http.StatusUnauthorized, gin.H{ c.JSON(http.StatusUnauthorized, gin.H{
"message": "permission validate error", "message": "permission validate error!",
}) })
c.Abort() c.Abort()
return return
} }
if adminRes {
c.Next() ok, err := e.Enforce(fmt.Sprint(c.GetUint("id")), permission, action)
return
}
res, err := e.Enforce(userName, permissionName, action)
if err != nil { if err != nil {
logger.Errorf("RBAC validate error: %s", err) logger.Errorf("RBAC validate error: %s", err)
c.JSON(http.StatusUnauthorized, gin.H{ c.JSON(http.StatusUnauthorized, gin.H{
"message": "permission validate error, please see log!", "message": "permission validate error!",
}) })
c.Abort() c.Abort()
return return
} }
if !res {
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{ c.JSON(http.StatusUnauthorized, gin.H{
"message": "permission deny", "message": "permission deny",
}) })
c.Abort() c.Abort()
return return
} }
c.Next()
c.Next()
} }
} }

View File

@ -18,12 +18,9 @@ package rbac
import ( import (
"errors" "errors"
"fmt"
"net/http" "net/http"
"regexp" "regexp"
"strings"
logger "d7y.io/dragonfly/v2/internal/dflog"
managermodel "d7y.io/dragonfly/v2/manager/model" managermodel "d7y.io/dragonfly/v2/manager/model"
"d7y.io/dragonfly/v2/pkg/util/stringutils" "d7y.io/dragonfly/v2/pkg/util/stringutils"
"github.com/casbin/casbin/v2" "github.com/casbin/casbin/v2"
@ -56,93 +53,83 @@ func NewEnforcer(gdb *gorm.DB) (*casbin.Enforcer, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
m, err := model.NewModelFromString(modelText) m, err := model.NewModelFromString(modelText)
if err != nil { if err != nil {
return nil, err return nil, err
} }
enforcer, err := casbin.NewEnforcer(m, adapter) enforcer, err := casbin.NewEnforcer(m, adapter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return enforcer, nil return enforcer, nil
} }
func InitRole(e *casbin.Enforcer, g *gin.Engine) error { func InitRBAC(e *casbin.Enforcer, g *gin.Engine) error {
systemRoles := SystemRoles(g) permissions := GetPermissions(g)
for _, permission := range permissions {
if _, err := e.AddPermissionForUser("root", permission.Object, "*"); err != nil {
return err
}
}
var err error if _, err := e.AddRoleForUser("1", "root"); err != nil {
for _, role := range systemRoles {
roleInfo := strings.Split(role, ":")
_, err = e.AddPolicy(role, roleInfo[0], roleInfo[1])
if err != nil {
return err 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 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) { func GetAPIGroupName(path string) (string, error) {
apiGroupRegexp := regexp.MustCompile(`^/api/v[0-9]+/([-_a-zA-Z]*)[/.*]*`) apiGroupRegexp := regexp.MustCompile(`^/api/v[0-9]+/([-_a-zA-Z]*)[/.*]*`)
matchs := apiGroupRegexp.FindStringSubmatch(path) matchs := apiGroupRegexp.FindStringSubmatch(path)
if len(matchs) != 2 { if len(matchs) != 2 {
return "", errors.New("faild to find api group") return "", errors.New("cannot find group name")
} }
return matchs[1], nil 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 { func HTTPMethodToAction(method string) string {
action := "read" action := "read"
if method == http.MethodDelete || method == http.MethodPatch || method == http.MethodPut || method == http.MethodPost { if method == http.MethodDelete || method == http.MethodPatch || method == http.MethodPut || method == http.MethodPost {
action = "*" action = "*"
} }

View File

@ -57,7 +57,7 @@ func TestGetApiGroupName(t *testing.T) {
path: "/api/user", path: "/api/user",
expect: func(t *testing.T, data string, err error) { expect: func(t *testing.T, data string, err error) {
assert := assert.New(t) 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: "", path: "",
expect: func(t *testing.T, data string, err error) { expect: func(t *testing.T, data string, err error) {
assert := assert.New(t) 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) { func TestHTTPMethodToAction(t *testing.T) {
tests := []struct { tests := []struct {
method string method string
@ -129,5 +103,4 @@ func TestHTTPMethodToAction(t *testing.T) {
t.Errorf("HttpMethodToAction(%v) = %v, want %v", tt.method, action, tt.exceptedAction) t.Errorf("HttpMethodToAction(%v) = %v, want %v", tt.method, action, tt.exceptedAction)
} }
} }
} }

View File

@ -79,14 +79,38 @@ func Init(console bool, verbose bool, publicPath string, service service.REST, e
apiv1 := r.Group("/api/v1") apiv1 := r.Group("/api/v1")
// User // User
ai := apiv1.Group("/users") u := apiv1.Group("/users")
ai.POST("/signin", jwt.LoginHandler) u.POST("/signin", jwt.LoginHandler)
ai.POST("/signout", jwt.LogoutHandler) u.POST("/signout", jwt.LogoutHandler)
ai.POST("/signup", h.SignUp) u.POST("/signup", h.SignUp)
ai.POST("/refresh_token", jwt.RefreshHandler) u.POST("/refresh_token", jwt.RefreshHandler)
ai.POST("/reset_password", h.ResetPassword) u.POST("/:id/reset_password", h.ResetPassword)
ai.POST("/:id/roles/:role_name", h.AddRoleToUser, jwt.MiddlewareFunc(), rbac) u.GET("/:id/roles", h.GetRolesForUser)
ai.DELETE("/:id/roles/:role_name", h.DeleteRoleForUser, jwt.MiddlewareFunc(), rbac) 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 // Scheduler Cluster
sc := apiv1.Group("/scheduler-clusters") 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(":id", h.GetCDN)
c.GET("", h.GetCDNs) 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 // Security Group
sg := apiv1.Group("/security-groups") sg := apiv1.Group("/security-groups")
sg.POST("", h.CreateSecurityGroup) sg.POST("", h.CreateSecurityGroup)

View File

@ -17,100 +17,10 @@
package service package service
import ( 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/permission/rbac"
"d7y.io/dragonfly/v2/manager/types"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func (s *rest) GetPermissions(g *gin.Engine) types.Permissions { func (s *rest) GetPermissions(g *gin.Engine) []rbac.Permission {
return rbac.GetAPIGroupNames(g) return rbac.GetPermissions(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
} }

52
manager/service/role.go Normal file
View File

@ -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)
}

View File

@ -21,6 +21,7 @@ import (
"d7y.io/dragonfly/v2/manager/database" "d7y.io/dragonfly/v2/manager/database"
"d7y.io/dragonfly/v2/manager/job" "d7y.io/dragonfly/v2/manager/job"
"d7y.io/dragonfly/v2/manager/model" "d7y.io/dragonfly/v2/manager/model"
"d7y.io/dragonfly/v2/manager/permission/rbac"
"d7y.io/dragonfly/v2/manager/types" "d7y.io/dragonfly/v2/manager/types"
"github.com/casbin/casbin/v2" "github.com/casbin/casbin/v2"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -33,19 +34,18 @@ type REST interface {
SignIn(types.SignInRequest) (*model.User, error) SignIn(types.SignInRequest) (*model.User, error)
SignUp(types.SignUpRequest) (*model.User, error) SignUp(types.SignUpRequest) (*model.User, error)
ResetPassword(uint, types.ResetPasswordRequest) error ResetPassword(uint, types.ResetPasswordRequest) error
GetRolesForUser(uint) ([]string, error)
AddRoleForUser(types.AddRoleForUserParams) (bool, error)
DeleteRoleForUser(types.DeleteRoleForUserParams) (bool, error)
AddRoleForUser(types.AddRoleForUserParams) error CreateRole(json types.CreateRoleRequest) error
DeleteRoleForUser(types.DeleteRoleForUserParams) error DestroyRole(string) (bool, error)
GetRole(string) [][]string
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 GetRoles() []string
GetRole(string) []map[string]string AddPermissionForRole(string, types.AddPermissionForRoleRequest) (bool, error)
GetRolesForUser(uint, string) ([]string, error) DeletePermissionForRole(string, types.DeletePermissionForRoleRequest) (bool, error)
GetPermissions(*gin.Engine) []rbac.Permission
CreateCDNCluster(types.CreateCDNClusterRequest) (*model.CDNCluster, error) CreateCDNCluster(types.CreateCDNClusterRequest) (*model.CDNCluster, error)
CreateCDNClusterWithSecurityGroupDomain(types.CreateCDNClusterRequest) (*model.CDNCluster, error) CreateCDNClusterWithSecurityGroupDomain(types.CreateCDNClusterRequest) (*model.CDNCluster, error)

View File

@ -88,18 +88,14 @@ func (s *rest) SignUp(json types.SignUpRequest) (*model.User, error) {
return &user, nil return &user, nil
} }
func (s *rest) AddRoleForUser(json types.AddRoleForUserParams) error { func (s *rest) GetRolesForUser(id uint) ([]string, error) {
if _, err := s.enforcer.AddRoleForUser(fmt.Sprint(json.ID), json.RoleName); err != nil { return s.enforcer.GetRolesForUser(fmt.Sprint(id))
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) error { func (s *rest) DeleteRoleForUser(json types.DeleteRoleForUserParams) (bool, error) {
if _, err := s.enforcer.DeleteRoleForUser(fmt.Sprint(json.ID), json.RoleName); err != nil { return s.enforcer.DeleteRoleForUser(fmt.Sprint(json.ID), json.Role)
return err
}
return nil
} }

View File

@ -16,22 +16,23 @@
package types 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 { type RoleParams struct {
RoleName string `uri:"role_name" binding:"required,min=1"` Role string `uri:"role" binding:"required"`
} }
type UserParams struct { type AddPermissionForRoleRequest struct {
ID uint `uri:"user_name" binding:"required"` rbac.Permission `json:",inline" binding:"required"`
} }
type ObjectPermission struct { type DeletePermissionForRoleRequest struct {
Object string `json:"object" binding:"required,min=1"` rbac.Permission `json:",inline" binding:"required"`
Action string `json:"action" binding:"required,oneof=read *"`
} }
type CreateRolePermissionRequest struct {
RoleName string `json:"role_name" binding:"required"`
Permissions []ObjectPermission `json:"permissions" binding:"dive"`
}
type Permissions []string

View File

@ -16,6 +16,10 @@
package types package types
type UserParams struct {
ID uint `uri:"id" binding:"required"`
}
type SignInRequest struct { type SignInRequest struct {
Name string `json:"name" binding:"required,min=3,max=10"` Name string `json:"name" binding:"required,min=3,max=10"`
Password string `json:"password" binding:"required,min=8,max=20"` Password string `json:"password" binding:"required,min=8,max=20"`
@ -37,10 +41,10 @@ type SignUpRequest struct {
type DeleteRoleForUserParams struct { type DeleteRoleForUserParams struct {
ID uint `uri:"id" binding:"required"` ID uint `uri:"id" binding:"required"`
RoleName string `uri:"role_name" binding:"required,min=1"` Role string `uri:"role" binding:"required,min=1"`
} }
type AddRoleForUserParams struct { type AddRoleForUserParams struct {
ID uint `uri:"id" binding:"required"` ID uint `uri:"id" binding:"required"`
RoleName string `uri:"role_name" binding:"required,min=1"` Role string `uri:"role" binding:"required,min=1"`
} }