feat: (rbac) permission validate / user tags

This commit is contained in:
xushuhui 2022-04-18 16:02:14 +08:00
parent dc8bd1cd1b
commit 8c013a613b
7 changed files with 264 additions and 63 deletions

117
internal/biz/account.go Normal file
View File

@ -0,0 +1,117 @@
package biz
import (
"errors"
"fmt"
"infini.sh/framework/core/global"
"infini.sh/framework/core/util"
"net/http"
"src/github.com/golang-jwt/jwt"
"strings"
"time"
)
type UserClaims struct {
*jwt.RegisteredClaims
*User
}
type User struct {
Username string `json:"username"`
UserId string `json:"user_id"`
ApiPermission map[string]struct{} `json:"api_permission"`
}
const Secret = "console"
func Login(username string, password string) (m map[string]interface{}, err error) {
u, _ := global.Env().GetConfig("bootstrap.username", "admin")
p, _ := global.Env().GetConfig("bootstrap.password", "admin")
if u != username || p != password {
err = errors.New("invalid username or password")
return
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaims{
User: &User{
Username: u,
UserId: "admin",
ApiPermission: map[string]struct{}{
"account.profile": struct{}{},
},
},
RegisteredClaims: &jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
},
})
tokenString, err := token.SignedString([]byte(Secret))
if err != nil {
return
}
m = util.MapStr{
"access_token": tokenString,
"username": u,
"userid": "admin",
}
return
}
func ValidateLogin(authorizationHeader string) (clams *UserClaims, err error) {
if authorizationHeader == "" {
err = errors.New("authorization header is empty")
return
}
fields := strings.Fields(authorizationHeader)
if fields[0] != "Bearer" || len(fields) != 2 {
err = errors.New("authorization header is invalid")
return
}
tokenString := fields[1]
token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(Secret), nil
})
if err != nil {
return
}
if clams, ok := token.Claims.(*UserClaims); ok && token.Valid {
return clams, nil
}
if clams.UserId == "" {
err = errors.New("user id is empty")
return
}
return
}
func ValidatePermission(r *http.Request, permissions string) (err error) {
reqUser, err := FromUserContext(r.Context())
if err != nil {
return
}
if reqUser.UserId == "" {
err = errors.New("user id is empty")
return
}
if reqUser.ApiPermission == nil {
err = errors.New("api permission is empty")
return
}
if _, ok := reqUser.ApiPermission[permissions]; !ok {
err = errors.New("permission denied")
return
}
return
}

29
internal/biz/context.go Normal file
View File

@ -0,0 +1,29 @@
package biz
import (
"context"
"errors"
)
const ctxUserKey = "user"
func NewUserContext(ctx context.Context, clam *UserClaims) context.Context {
return context.WithValue(ctx, ctxUserKey, clam)
}
func FromUserContext(ctx context.Context) (*User, error) {
ctxUser := ctx.Value(ctxUserKey)
if ctxUser == nil {
return nil, errors.New("user not found")
}
reqUser, ok := ctxUser.(*UserClaims)
if !ok {
return nil, errors.New("invalid context user")
}
return reqUser.User, nil
}
func NewPermissionContext(ctx context.Context) {
}
func FromPermissionContext(ctx context.Context) {
}

View File

@ -1,8 +1,38 @@
package middleware
func LoginRequired() {
import (
"infini.sh/console/internal/biz"
httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/util"
"net/http"
)
}
func PermissionRequired() {
func LoginRequired(h httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
claims, err := biz.ValidateLogin(r.Header.Get("Authorization"))
if err != nil {
w = handleError(w, err)
return
}
r = r.WithContext(biz.NewUserContext(r.Context(), claims))
h(w, r, ps)
}
}
func PermissionRequired(h httprouter.Handle, permissions string) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
err := biz.ValidatePermission(r, permissions)
if err != nil {
w = handleError(w, err)
return
}
h(w, r, ps)
}
}
func handleError(w http.ResponseWriter, err error) http.ResponseWriter {
w.Header().Set("Content-type", util.ContentTypeJson)
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"error":"` + err.Error() + `"}`))
return w
}

View File

@ -1,6 +1,9 @@
package rbac
import "infini.sh/framework/core/orm"
import (
"infini.sh/framework/core/orm"
"time"
)
type Role struct {
orm.ORMObjectBase
@ -20,3 +23,28 @@ type ElasticsearchPermission struct {
ClusterPrivilege []string `json:"cluster_privilege" elastic_mapping:"cluster_privilege:{type:object}"`
IndexPrivilege []string `json:"index_privilege" elastic_mapping:"index_privilege:{type:object}"`
}
type ConsoleOperate struct {
UserId string `json:"user_id" elastic_mapping:"user_id:{type:keyword}"`
}
type Operation struct {
Id string `json:"id"`
Timestamp time.Time `json:"timestamp"`
Metadata struct {
Labels struct {
Userid string `json:"userid"`
Username string `json:"username"`
} `json:"labels"`
Category string `json:"category"`
Group string `json:"group"`
Name string `json:"name"`
Type string `json:"type"`
} `json:"metadata"`
Changelog []struct {
From string `json:"from"`
Path []string `json:"path"`
To string `json:"to"`
Type string `json:"type"`
} `json:"changelog"`
Payload interface{} `json:"payload"`
}

View File

@ -10,6 +10,7 @@ type User struct {
Phone string `json:"phone" elastic_mapping:"phone:{type:keyword}"`
Email string `json:"email" elastic_mapping:"email:{type:keyword}"`
Roles []UserRole `json:"roles" elastic_mapping:"roles:{type:text}"`
Tags []string `json:"tags" elastic_mapping:"tags:{type:text}"`
}
type UserRole struct {
Id string `json:"id"`

View File

@ -1,9 +1,11 @@
package account
import (
"infini.sh/console/internal/biz"
"infini.sh/console/internal/dto"
m "infini.sh/console/internal/middleware"
"infini.sh/framework/core/api"
"infini.sh/framework/core/api/router"
"infini.sh/framework/core/global"
"infini.sh/framework/core/util"
"net/http"
)
@ -14,54 +16,35 @@ type Account struct {
func init() {
account := Account{}
api.HandleAPIMethod(api.POST, "/account/login", account.AccountLogin)
api.HandleAPIMethod(api.POST, "/account/login", account.Login)
api.HandleAPIMethod(api.GET, "/account/current_user", account.CurrentUser)
api.HandleAPIMethod(api.DELETE, "/account/logout", account.Logout)
api.HandleAPIMethod(api.GET, "/account/profile",
m.LoginRequired(account.Profile))
}
var userInSession string="user_in_session"
const userInSession = "user_in_session"
func (handler Account)AccountLogin(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
func (h Account) Login(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
//{"userName":"admin","password":"111111","type":"account"}
json,err:=handler.GetJSON(req)
var req dto.Login
err := h.DecodeJSON(r, &req)
if err != nil {
handler.Error(w,err)
return
}
userName,err:=json.String("userName")
if err!=nil{
handler.Error(w,err)
return
}
password,err:=json.String("password")
if err!=nil{
handler.Error(w,err)
h.Error(w, err)
return
}
u,_:=global.Env().GetConfig("bootstrap.username","admin")
p,_:=global.Env().GetConfig("bootstrap.password","admin")
if u==userName&&p==password{
data := util.MapStr{
"status": "ok",
"type": "account",
"currentAuthority": "admin",
"userid": "10001",
}
api.SetSession(w,req, userInSession,userName)
handler.WriteJSON(w, data, http.StatusOK)
}else{
data := util.MapStr{
"status": "error",
"type": "account",
"currentAuthority": "guest",
}
handler.WriteJSON(w, data, http.StatusOK)
data, err := biz.Login(req.Username, req.Password)
if err != nil {
h.Error(w, err)
return
}
h.WriteJSON(w, data, http.StatusOK)
}
func (handler Account)CurrentUser(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
func (h Account) CurrentUser(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
exists, user := api.GetSession(w, req, userInSession)
if exists {
@ -94,13 +77,25 @@ func (handler Account)CurrentUser(w http.ResponseWriter, req *http.Request, ps h
"phone": "4001399200",
}
handler.WriteJSON(w, data,200)
h.WriteJSON(w, data, 200)
} else {
data := util.MapStr{
"status": "error",
"type": "account",
"currentAuthority": "guest",
}
handler.WriteJSON(w, data, 403)
h.WriteJSON(w, data, 403)
}
}
func (h Account) Logout(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
}
func (h Account) Profile(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
reqUser, err := biz.FromUserContext(r.Context())
if err != nil {
h.Error(w, err)
return
}
h.WriteJSON(w, reqUser, 200)
return
}

View File

@ -3,6 +3,7 @@ package rbac
import (
"encoding/json"
"infini.sh/console/internal/biz"
m "infini.sh/console/internal/middleware"
"infini.sh/framework/core/api"
"infini.sh/framework/core/util"
@ -18,10 +19,10 @@ func registerRouter() {
r := Rbac{}
api.HandleAPIMethod(api.GET, "/permission/:type", r.ListPermission)
api.HandleAPIMethod(api.POST, "/role/:type", r.CreateRole)
api.HandleAPIMethod(api.GET, "/role/:id", r.GetRole)
api.HandleAPIMethod(api.GET, "/role/:id", m.LoginRequired(r.GetRole))
api.HandleAPIMethod(api.DELETE, "/role/:id", r.DeleteRole)
api.HandleAPIMethod(api.PUT, "/role/:id", r.UpdateRole)
api.HandleAPIMethod(api.GET, "/role/_search", r.SearchRole)
api.HandleAPIMethod(api.GET, "/role/_search", m.LoginRequired(m.PermissionRequired(r.SearchRole, "search.role")))
api.HandleAPIMethod(api.POST, "/user", r.CreateUser)
api.HandleAPIMethod(api.GET, "/user/:id", r.GetUser)