feat: (rbac) permission validate / user tags
This commit is contained in:
parent
dc8bd1cd1b
commit
8c013a613b
|
@ -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
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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/api/router"
|
||||
"infini.sh/framework/core/util"
|
||||
"net/http"
|
||||
)
|
||||
|
@ -13,94 +15,87 @@ type Account struct {
|
|||
}
|
||||
|
||||
func init() {
|
||||
account:=Account{}
|
||||
api.HandleAPIMethod(api.POST, "/account/login", account.AccountLogin)
|
||||
account := Account{}
|
||||
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)
|
||||
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)
|
||||
var req dto.Login
|
||||
err := h.DecodeJSON(r, &req)
|
||||
if err != nil {
|
||||
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{
|
||||
data:=util.MapStr{
|
||||
"name": user,
|
||||
"avatar": "",
|
||||
"userid": "10001",
|
||||
"email": "hello@infini.ltd",
|
||||
exists, user := api.GetSession(w, req, userInSession)
|
||||
if exists {
|
||||
data := util.MapStr{
|
||||
"name": user,
|
||||
"avatar": "",
|
||||
"userid": "10001",
|
||||
"email": "hello@infini.ltd",
|
||||
"signature": "极限科技 - 专业的开源搜索与实时数据分析整体解决方案提供商。",
|
||||
"title": "首席设计师",
|
||||
"group": "INFINI Labs",
|
||||
"title": "首席设计师",
|
||||
"group": "INFINI Labs",
|
||||
"tags": []util.MapStr{
|
||||
{
|
||||
"key": "0",
|
||||
"key": "0",
|
||||
"label": "很有想法的",
|
||||
}},
|
||||
"notifyCount": 12,
|
||||
"country": "China",
|
||||
"country": "China",
|
||||
"geographic": util.MapStr{
|
||||
"province": util.MapStr{
|
||||
"label": "湖南省",
|
||||
"key": "330000",
|
||||
"key": "330000",
|
||||
},
|
||||
"city": util.MapStr{
|
||||
"label": "长沙市",
|
||||
"key": "330100",
|
||||
"key": "330100",
|
||||
},
|
||||
},
|
||||
"address": "岳麓区湘江金融中心",
|
||||
"phone": "4001399200",
|
||||
"phone": "4001399200",
|
||||
}
|
||||
|
||||
handler.WriteJSON(w, data,200)
|
||||
}else{
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue